Merge "Ensure that we destroy view before adding another" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b5f398b..b3e8ea8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.view.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
@@ -36,6 +37,7 @@
     ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
     ":com.android.input.flags-aconfig-java{.generated_srcjars}",
     ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+    ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
     ":telecom_flags_core_java_lib{.generated_srcjars}",
     ":telephony_flags_core_java_lib{.generated_srcjars}",
     ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
@@ -53,6 +55,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}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
     ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
     ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
@@ -63,6 +66,7 @@
     ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -263,6 +267,23 @@
 }
 
 // VirtualDeviceManager
+cc_aconfig_library {
+    name: "android.companion.virtualdevice.flags-aconfig-cc",
+    aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
+}
+
+java_aconfig_library {
+    name: "android.companion.virtualdevice.flags-aconfig-java",
+    aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+aconfig_declarations {
+    name: "android.companion.virtualdevice.flags-aconfig",
+    package: "android.companion.virtualdevice.flags",
+    srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
+}
+
 java_aconfig_library {
     name: "android.companion.virtual.flags-aconfig-java",
     aconfig_declarations: "android.companion.virtual.flags-aconfig",
@@ -564,6 +585,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Pinner Service
+aconfig_declarations {
+    name: "com.android.server.flags.pinner-aconfig",
+    package: "com.android.server.flags",
+    srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"],
+}
+
+java_aconfig_library {
+    name: "com.android.server.flags.pinner-aconfig-java",
+    aconfig_declarations: "com.android.server.flags.pinner-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Voice
 aconfig_declarations {
     name: "android.service.voice.flags-aconfig",
@@ -664,6 +698,32 @@
     aconfig_declarations: "device_policy_aconfig_flags",
 }
 
+// Chooser / "Sharesheet"
+aconfig_declarations {
+    name: "android.service.chooser.flags-aconfig",
+    package: "android.service.chooser",
+    srcs: ["core/java/android/service/chooser/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.chooser.flags-aconfig-java",
+    aconfig_declarations: "android.service.chooser.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// JobScheduler
+aconfig_declarations {
+    name: "framework-jobscheduler-job.flags-aconfig",
+    package: "android.app.job",
+    srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"],
+}
+
+java_aconfig_library {
+    name: "framework-jobscheduler-job.flags-aconfig-java",
+    aconfig_declarations: "framework-jobscheduler-job.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Notifications
 aconfig_declarations {
     name: "android.service.notification.flags-aconfig",
@@ -723,6 +783,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.hardware.usb.flags-aconfig-java-host",
+    aconfig_declarations: "android.hardware.usb.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // WindowingTools
 aconfig_declarations {
     name: "android.tracing.flags-aconfig",
@@ -748,3 +815,19 @@
     aconfig_declarations: "android.appwidget.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// WebView
+aconfig_declarations {
+    name: "android.webkit.flags-aconfig",
+    package: "android.webkit",
+    srcs: [
+        "core/java/android/webkit/*.aconfig",
+        "services/core/java/com/android/server/webkit/*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "android.webkit.flags-aconfig-java",
+    aconfig_declarations: "android.webkit.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index a402c576..c1fb41f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -140,6 +140,9 @@
         // For the generated R.java and Manifest.java
         ":framework-res{.aapt.srcjar}",
 
+        // Java/AIDL sources to be moved out to CrashRecovery module
+        ":framework-crashrecovery-sources",
+
         // etc.
         ":framework-javastream-protos",
         ":statslog-framework-java-gen", // FrameworkStatsLog.java
@@ -414,13 +417,25 @@
     ],
 }
 
+// Collection of non updatable unbundled jars. The list here should match
+// |non_updatable_modules| variable in frameworks/base/api/api.go.
+java_library {
+    name: "framework-non-updatable-unbundled-impl-libs",
+    static_libs: [
+        "framework-location.impl",
+        "framework-nfc.impl",
+    ],
+    sdk_version: "core_platform",
+    installable: false,
+}
+
 // Separated so framework-minus-apex-defaults can be used without the libs dependency
 java_defaults {
     name: "framework-minus-apex-with-libs-defaults",
     defaults: ["framework-minus-apex-defaults"],
     libs: [
         "framework-virtualization.stubs.module_lib",
-        "framework-location.impl",
+        "framework-non-updatable-unbundled-impl-libs",
     ],
 }
 
@@ -451,7 +466,7 @@
     stem: "framework",
     apex_available: ["//apex_available:platform"],
     visibility: [
-        "//frameworks/base/location",
+        "//frameworks/base:__subpackages__",
     ],
     compile_dex: false,
     headers_only: true,
@@ -514,8 +529,8 @@
     installable: false, // this lib is a build-only library
     static_libs: [
         "app-compat-annotations",
-        "framework-location.impl",
         "framework-minus-apex",
+        "framework-non-updatable-unbundled-impl-libs",
         "framework-updatable-stubs-module_libs_api",
     ],
     sdk_version: "core_platform",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index b497871..ca73378 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -32,7 +32,6 @@
     cmd: "$(location hoststubgen) " +
         "@$(location ravenwood/ravenwood-standard-options.txt) " +
 
-        "--out-stub-jar $(location ravenwood_stub.jar) " +
         "--out-impl-jar $(location ravenwood.jar) " +
 
         "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
@@ -49,7 +48,6 @@
     ],
     out: [
         "ravenwood.jar",
-        "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional.
 
         // Following files are created just as FYI.
         "hoststubgen_keep_all.txt",
@@ -81,7 +79,7 @@
         "hoststubgen-helper-framework-runtime.ravenwood",
         "junit",
         "truth",
-        "ravenwood-junit",
+        "ravenwood-junit-impl",
         "android.test.mock",
     ],
 }
diff --git a/THERMAL_OWNERS b/THERMAL_OWNERS
new file mode 100644
index 0000000..b95b7e8
--- /dev/null
+++ b/THERMAL_OWNERS
@@ -0,0 +1,3 @@
+lpy@google.com
+wvw@google.com
+xwxw@google.com
diff --git a/apct-tests/perftests/OWNERS b/apct-tests/perftests/OWNERS
index 4c57e64..8ff3f9b 100644
--- a/apct-tests/perftests/OWNERS
+++ b/apct-tests/perftests/OWNERS
@@ -1,12 +1,11 @@
-balejs@google.com
 carmenjackson@google.com
-cfijalkovich@google.com
 dualli@google.com
 edgararriaga@google.com
-jpakaravoor@google.com
+jdduke@google.com
 jreck@google.com #{LAST_RESORT_SUGGESTION}
 kevinjeon@google.com
 philipcuadra@google.com
+shayba@google.com
 shombert@google.com
 timmurray@google.com
 wessam@google.com
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 9366ff2d..e1b3241 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -66,6 +66,7 @@
     errorprone: {
         javacflags: [
             "-Xep:ReturnValueIgnored:WARN",
+            "-Xep:UnnecessaryStringBuilder:OFF",
         ],
     },
 }
diff --git a/apct-tests/perftests/core/res/drawable-nodpi/fountain_night.jpg b/apct-tests/perftests/core/res/drawable-nodpi/fountain_night.jpg
new file mode 100644
index 0000000..d8b2d75
--- /dev/null
+++ b/apct-tests/perftests/core/res/drawable-nodpi/fountain_night.jpg
Binary files differ
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index f84a0d0..e5a06c9 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -16,20 +16,29 @@
 
 package android.graphics.perftests;
 
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Color;
+import android.graphics.ImageDecoder;
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RenderNode;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 
+import com.android.perftests.core.R;
+
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.IOException;
+
 @LargeTest
 public class CanvasPerfTest {
     @Rule
@@ -93,4 +102,38 @@
             node.end(canvas);
         }
     }
+
+    @Test
+    public void testCreateScaledBitmap() throws IOException {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getContext();
+        Bitmap source = ImageDecoder.decodeBitmap(
+                ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
+                (decoder, info, source1) -> {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                });
+        source.setGainmap(null);
+
+        while (state.keepRunning()) {
+            Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
+                    .recycle();
+        }
+    }
+
+    @Test
+    public void testCreateScaledBitmapWithGainmap() throws IOException {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getContext();
+        Bitmap source = ImageDecoder.decodeBitmap(
+                ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
+                (decoder, info, source1) -> {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                });
+        assertTrue(source.hasGainmap());
+
+        while (state.keepRunning()) {
+            Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
+                    .recycle();
+        }
+    }
 }
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
index 3a11417..d3b4f23 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
@@ -52,9 +52,8 @@
     private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
             "applyPostLayoutPolicy",
             "applySurfaceChanges",
-            "AppTransitionReady",
-            "closeSurfaceTransaction",
-            "openSurfaceTransaction",
+            "onTransactionReady",
+            "applyTransaction",
             "performLayout",
             "performSurfacePlacement",
             "prepareSurfaces",
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c42c7ca..2ace602 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -128,6 +128,7 @@
         final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
         final InsetsState mOutInsetsState = new InsetsState();
         final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
+        final Bundle mOutBundle = new Bundle();
         final IWindow mWindow;
         final View mView;
         final WindowManager.LayoutParams mParams;
@@ -136,7 +137,7 @@
         final SurfaceControl mOutSurfaceControl;
 
         final IntSupplier mViewVisibility;
-
+        int mRelayoutSeq;
         int mFlags;
 
         RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -152,10 +153,11 @@
         void runBenchmark(BenchmarkState state) throws RemoteException {
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
+                mRelayoutSeq++;
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+                        mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
                         mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
-                        mOutControls, new Bundle());
+                        mOutControls, mOutBundle);
             }
         }
     }
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index b87e42e..72816e4 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -112,7 +112,7 @@
                 state.addExtraResult("add", elapsedTimeNsOfAdd);
 
                 startTime = SystemClock.elapsedRealtimeNanos();
-                session.remove(this);
+                session.remove(asBinder());
                 final long elapsedTimeNsOfRemove = SystemClock.elapsedRealtimeNanos() - startTime;
                 state.addExtraResult("remove", elapsedTimeNsOfRemove);
 
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
new file mode 100644
index 0000000..f5e33a80
--- /dev/null
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.job"
+
+flag {
+    name: "job_debug_info_apis"
+    namespace: "backstage_power"
+    description: "Add APIs to let apps attach debug information to jobs"
+    bug: "293491637"
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 9961c4f..742ed5f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -26,6 +26,7 @@
 import static android.util.TimeUtils.formatDuration;
 
 import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,13 +48,17 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.Trace;
+import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
@@ -423,6 +428,15 @@
      */
     public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3;
 
+    /** @hide */
+    public static final int MAX_NUM_DEBUG_TAGS = 32;
+
+    /** @hide */
+    public static final int MAX_DEBUG_TAG_LENGTH = 127;
+
+    /** @hide */
+    public static final int MAX_TRACE_TAG_LENGTH = Trace.MAX_SECTION_NAME_LEN;
+
     @UnsupportedAppUsage
     private final int jobId;
     private final PersistableBundle extras;
@@ -454,6 +468,9 @@
     private final int mPriority;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int flags;
+    private final ArraySet<String> mDebugTags;
+    @Nullable
+    private final String mTraceTag;
 
     /**
      * Unique job id associated with this application (uid).  This is the same job ID
@@ -724,6 +741,33 @@
     }
 
     /**
+     * @see JobInfo.Builder#addDebugTag(String)
+     */
+    @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+    @NonNull
+    public Set<String> getDebugTags() {
+        return Collections.unmodifiableSet(mDebugTags);
+    }
+
+    /**
+     * @see JobInfo.Builder#addDebugTag(String)
+     * @hide
+     */
+    @NonNull
+    public ArraySet<String> getDebugTagsArraySet() {
+        return mDebugTags;
+    }
+
+    /**
+     * @see JobInfo.Builder#setTraceTag(String)
+     */
+    @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+    @Nullable
+    public String getTraceTag() {
+        return mTraceTag;
+    }
+
+    /**
      * @see JobInfo.Builder#setExpedited(boolean)
      */
     public boolean isExpedited() {
@@ -860,6 +904,12 @@
         if (flags != j.flags) {
             return false;
         }
+        if (!mDebugTags.equals(j.mDebugTags)) {
+            return false;
+        }
+        if (!Objects.equals(mTraceTag, j.mTraceTag)) {
+            return false;
+        }
         return true;
     }
 
@@ -904,6 +954,12 @@
         hashCode = 31 * hashCode + mBias;
         hashCode = 31 * hashCode + mPriority;
         hashCode = 31 * hashCode + flags;
+        if (mDebugTags.size() > 0) {
+            hashCode = 31 * hashCode + mDebugTags.hashCode();
+        }
+        if (mTraceTag != null) {
+            hashCode = 31 * hashCode + mTraceTag.hashCode();
+        }
         return hashCode;
     }
 
@@ -946,6 +1002,17 @@
         mBias = in.readInt();
         mPriority = in.readInt();
         flags = in.readInt();
+        final int numDebugTags = in.readInt();
+        mDebugTags = new ArraySet<>();
+        for (int i = 0; i < numDebugTags; ++i) {
+            final String tag = in.readString();
+            if (tag == null) {
+                throw new IllegalStateException("malformed parcel");
+            }
+            mDebugTags.add(tag.intern());
+        }
+        final String traceTag = in.readString();
+        mTraceTag = traceTag == null ? null : traceTag.intern();
     }
 
     private JobInfo(JobInfo.Builder b) {
@@ -978,6 +1045,8 @@
         mBias = b.mBias;
         mPriority = b.mPriority;
         flags = b.mFlags;
+        mDebugTags = b.mDebugTags;
+        mTraceTag = b.mTraceTag;
     }
 
     @Override
@@ -1024,6 +1093,14 @@
         out.writeInt(mBias);
         out.writeInt(mPriority);
         out.writeInt(this.flags);
+        // Explicitly write out values here to avoid double looping to intern the strings
+        // when unparcelling.
+        final int numDebugTags = mDebugTags.size();
+        out.writeInt(numDebugTags);
+        for (int i = 0; i < numDebugTags; ++i) {
+            out.writeString(mDebugTags.valueAt(i));
+        }
+        out.writeString(mTraceTag);
     }
 
     public static final @android.annotation.NonNull Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
@@ -1168,6 +1245,8 @@
         private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
         /** Easy way to track whether the client has tried to set a back-off policy. */
         private boolean mBackoffPolicySet = false;
+        private final ArraySet<String> mDebugTags = new ArraySet<>();
+        private String mTraceTag;
 
         /**
          * Initialize a new Builder to construct a {@link JobInfo}.
@@ -1222,6 +1301,51 @@
             mPriority = job.getPriority();
         }
 
+        /**
+         * Add a debug tag to help track what this job is for. The tags may show in debug dumps
+         * or app metrics. Do not put personally identifiable information (PII) in the tag.
+         * <p>
+         * Tags have the following requirements:
+         * <ul>
+         *   <li>Tags cannot be more than 127 characters.</li>
+         *   <li>
+         *       Since leading and trailing whitespace can lead to hard-to-debug issues,
+         *       tags should not include leading or trailing whitespace.
+         *       All tags will be {@link String#trim() trimmed}.
+         *   </li>
+         *   <li>An empty String (after trimming) is not allowed.</li>
+         *   <li>Should not have personally identifiable information (PII).</li>
+         *   <li>A job cannot have more than 32 tags.</li>
+         * </ul>
+         *
+         * @param tag A debug tag that helps describe what the job is for.
+         * @return This object for method chaining
+         */
+        @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+        @NonNull
+        public Builder addDebugTag(@NonNull String tag) {
+            mDebugTags.add(validateDebugTag(tag));
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public void addDebugTags(@NonNull Set<String> tags) {
+            mDebugTags.addAll(tags);
+        }
+
+        /**
+         * Remove a tag set via {@link #addDebugTag(String)}.
+         * @param tag The tag to remove
+         * @return This object for method chaining
+         */
+        @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+        @NonNull
+        public Builder removeDebugTag(@NonNull String tag) {
+            mDebugTags.remove(tag);
+            return this;
+        }
+
         /** @hide */
         @NonNull
         @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
@@ -1997,6 +2121,24 @@
         }
 
         /**
+         * Set a tag that will be used in {@link android.os.Trace traces}.
+         * Since this is a trace tag, it must follow the rules set in
+         * {@link android.os.Trace#beginSection(String)}, such as it cannot be more
+         * than 127 Unicode code units.
+         * Additionally, since leading and trailing whitespace can lead to hard-to-debug issues,
+         * they will be {@link String#trim() trimmed}.
+         * An empty String (after trimming) is not allowed.
+         * @param traceTag The tag to use in traces.
+         * @return This object for method chaining
+         */
+        @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+        @NonNull
+        public Builder setTraceTag(@Nullable String traceTag) {
+            mTraceTag = validateTraceTag(traceTag);
+            return this;
+        }
+
+        /**
          * @return The job object to hand to the JobScheduler. This object is immutable.
          */
         public JobInfo build() {
@@ -2209,6 +2351,62 @@
                         "A user-initiated data transfer job must specify a valid network type");
             }
         }
+
+        if (mDebugTags.size() > MAX_NUM_DEBUG_TAGS) {
+            throw new IllegalArgumentException(
+                    "Can't have more than " + MAX_NUM_DEBUG_TAGS + " tags");
+        }
+        final ArraySet<String> validatedDebugTags = new ArraySet<>();
+        for (int i = 0; i < mDebugTags.size(); ++i) {
+            validatedDebugTags.add(validateDebugTag(mDebugTags.valueAt(i)));
+        }
+        mDebugTags.clear();
+        mDebugTags.addAll(validatedDebugTags);
+
+        validateTraceTag(mTraceTag);
+    }
+
+    /**
+     * Returns a sanitized debug tag if valid, or throws an exception if not.
+     * @hide
+     */
+    @NonNull
+    public static String validateDebugTag(@Nullable String debugTag) {
+        if (debugTag == null) {
+            throw new NullPointerException("debug tag cannot be null");
+        }
+        debugTag = debugTag.trim();
+        if (debugTag.isEmpty()) {
+            throw new IllegalArgumentException("debug tag cannot be empty");
+        }
+        if (debugTag.length() > MAX_DEBUG_TAG_LENGTH) {
+            throw new IllegalArgumentException(
+                    "debug tag cannot be more than " + MAX_DEBUG_TAG_LENGTH + " characters");
+        }
+        return debugTag.intern();
+    }
+
+    /**
+     * Returns a sanitized trace tag if valid, or throws an exception if not.
+     * @hide
+     */
+    @Nullable
+    public static String validateTraceTag(@Nullable String traceTag) {
+        if (traceTag == null) {
+            return null;
+        }
+        traceTag = traceTag.trim();
+        if (traceTag.isEmpty()) {
+            throw new IllegalArgumentException("trace tag cannot be empty");
+        }
+        if (traceTag.length() > MAX_TRACE_TAG_LENGTH) {
+            throw new IllegalArgumentException(
+                    "traceTag tag cannot be more than " + MAX_TRACE_TAG_LENGTH + " characters");
+        }
+        if (traceTag.contains("|") || traceTag.contains("\n") || traceTag.contains("\0")) {
+            throw new IllegalArgumentException("Trace tag cannot contain |, \\n, or \\0");
+        }
+        return traceTag.intern();
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 158d914..e6c94d8 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -32,6 +32,7 @@
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -41,6 +42,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -81,6 +83,7 @@
 import android.os.UserHandle;
 import android.os.WearModeManagerInternal;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
@@ -109,6 +112,7 @@
 import com.android.server.deviceidle.IDeviceIdleConstraint;
 import com.android.server.deviceidle.TvConstraintController;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -1020,7 +1024,8 @@
      * global Settings. Any access to this class or its fields should be done while
      * holding the DeviceIdleController lock.
      */
-    public final class Constants implements DeviceConfig.OnPropertiesChangedListener {
+    public final class Constants extends ContentObserver
+            implements DeviceConfig.OnPropertiesChangedListener {
         // Key names stored in the settings value.
         private static final String KEY_FLEX_TIME_SHORT = "flex_time_short";
         private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT =
@@ -1396,6 +1401,7 @@
         /**
          * Amount of time we would like to whitelist an app that is handling a
          * {@link android.app.PendingIntent} triggered by a {@link android.app.Notification}.
+         *
          * @see #KEY_NOTIFICATION_ALLOWLIST_DURATION_MS
          */
         public long NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
@@ -1413,9 +1419,14 @@
          */
         public boolean USE_MODE_MANAGER = mDefaultUseModeManager;
 
+        private final ContentResolver mResolver;
         private final boolean mSmallBatteryDevice;
+        private final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
 
-        public Constants() {
+        public Constants(Handler handler, ContentResolver resolver) {
+            super(handler);
+            mResolver = resolver;
             initDefault();
             mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
             if (mSmallBatteryDevice) {
@@ -1424,8 +1435,14 @@
             }
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DEVICE_IDLE,
                     AppSchedulingModuleThread.getExecutor(), this);
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
+                    false, this);
             // Load all the constants.
-            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
+            updateSettingsConstantLocked();
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(
+                    DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
+            updateConstantsLocked();
         }
 
         private void initDefault() {
@@ -1574,188 +1591,166 @@
             return (!COMPRESS_TIME || defTimeout < compTimeout) ? defTimeout : compTimeout;
         }
 
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (DeviceIdleController.this) {
+                updateSettingsConstantLocked();
+                updateConstantsLocked();
+            }
+        }
+
+        private void updateSettingsConstantLocked() {
+            try {
+                mUserSettingDeviceConfigMediator.setSettingsString(
+                        Settings.Global.getString(mResolver,
+                                Settings.Global.DEVICE_IDLE_CONSTANTS));
+            } catch (IllegalArgumentException e) {
+                // Failed to parse the settings string, log this and move on with previous values.
+                Slog.e(TAG, "Bad device idle settings", e);
+            }
+        }
 
         @Override
         public void onPropertiesChanged(DeviceConfig.Properties properties) {
             synchronized (DeviceIdleController.this) {
-                for (String name : properties.getKeyset()) {
-                    if (name == null) {
-                        continue;
-                    }
-                    switch (name) {
-                        case KEY_FLEX_TIME_SHORT:
-                            FLEX_TIME_SHORT = properties.getLong(
-                                    KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort);
-                            break;
-                        case KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT:
-                            LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
-                                    mDefaultLightIdleAfterInactiveTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT:
-                            LIGHT_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX:
-                            LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX,
-                                    mDefaultLightIdleTimeoutInitialFlex);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX:
-                            LIGHT_IDLE_TIMEOUT_MAX_FLEX = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX,
-                                    mDefaultLightIdleTimeoutMaxFlex);
-                            break;
-                        case KEY_LIGHT_IDLE_FACTOR:
-                            LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat(
-                                    KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor));
-                            break;
-                        case KEY_LIGHT_IDLE_INCREASE_LINEARLY:
-                            LIGHT_IDLE_INCREASE_LINEARLY = properties.getBoolean(
-                                    KEY_LIGHT_IDLE_INCREASE_LINEARLY,
-                                    mDefaultLightIdleIncreaseLinearly);
-                            break;
-                        case KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS:
-                            LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = properties.getLong(
-                                    KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS,
-                                    mDefaultLightIdleLinearIncreaseFactorMs);
-                            break;
-                        case KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS:
-                            LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = properties.getLong(
-                                    KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS,
-                                    mDefaultLightIdleFlexLinearIncreaseFactorMs);
-                            break;
-                        case KEY_LIGHT_MAX_IDLE_TIMEOUT:
-                            LIGHT_MAX_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET:
-                            LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = properties.getLong(
-                                    KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
-                                    mDefaultLightIdleMaintenanceMinBudget);
-                            break;
-                        case KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET:
-                            LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = properties.getLong(
-                                    KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
-                                    mDefaultLightIdleMaintenanceMaxBudget);
-                            break;
-                        case KEY_MIN_LIGHT_MAINTENANCE_TIME:
-                            MIN_LIGHT_MAINTENANCE_TIME = properties.getLong(
-                                    KEY_MIN_LIGHT_MAINTENANCE_TIME,
-                                    mDefaultMinLightMaintenanceTime);
-                            break;
-                        case KEY_MIN_DEEP_MAINTENANCE_TIME:
-                            MIN_DEEP_MAINTENANCE_TIME = properties.getLong(
-                                    KEY_MIN_DEEP_MAINTENANCE_TIME,
-                                    mDefaultMinDeepMaintenanceTime);
-                            break;
-                        case KEY_INACTIVE_TIMEOUT:
-                            final long defaultInactiveTimeout = mSmallBatteryDevice
-                                    ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY
-                                    : mDefaultInactiveTimeout;
-                            INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout);
-                            break;
-                        case KEY_SENSING_TIMEOUT:
-                            SENSING_TIMEOUT = properties.getLong(
-                                    KEY_SENSING_TIMEOUT, mDefaultSensingTimeout);
-                            break;
-                        case KEY_LOCATING_TIMEOUT:
-                            LOCATING_TIMEOUT = properties.getLong(
-                                    KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout);
-                            break;
-                        case KEY_LOCATION_ACCURACY:
-                            LOCATION_ACCURACY = properties.getFloat(
-                                    KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy);
-                            break;
-                        case KEY_MOTION_INACTIVE_TIMEOUT:
-                            MOTION_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout);
-                            break;
-                        case KEY_MOTION_INACTIVE_TIMEOUT_FLEX:
-                            MOTION_INACTIVE_TIMEOUT_FLEX = properties.getLong(
-                                    KEY_MOTION_INACTIVE_TIMEOUT_FLEX,
-                                    mDefaultMotionInactiveTimeoutFlex);
-                            break;
-                        case KEY_IDLE_AFTER_INACTIVE_TIMEOUT:
-                            final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice
-                                    ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY
-                                    : mDefaultIdleAfterInactiveTimeout;
-                            IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
-                                    defaultIdleAfterInactiveTimeout);
-                            break;
-                        case KEY_IDLE_PENDING_TIMEOUT:
-                            IDLE_PENDING_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout);
-                            break;
-                        case KEY_MAX_IDLE_PENDING_TIMEOUT:
-                            MAX_IDLE_PENDING_TIMEOUT = properties.getLong(
-                                    KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout);
-                            break;
-                        case KEY_IDLE_PENDING_FACTOR:
-                            IDLE_PENDING_FACTOR = properties.getFloat(
-                                    KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor);
-                            break;
-                        case KEY_QUICK_DOZE_DELAY_TIMEOUT:
-                            QUICK_DOZE_DELAY_TIMEOUT = properties.getLong(
-                                    KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout);
-                            break;
-                        case KEY_IDLE_TIMEOUT:
-                            IDLE_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_TIMEOUT, mDefaultIdleTimeout);
-                            break;
-                        case KEY_MAX_IDLE_TIMEOUT:
-                            MAX_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout);
-                            break;
-                        case KEY_IDLE_FACTOR:
-                            IDLE_FACTOR = properties.getFloat(KEY_IDLE_FACTOR, mDefaultIdleFactor);
-                            break;
-                        case KEY_MIN_TIME_TO_ALARM:
-                            MIN_TIME_TO_ALARM = properties.getLong(
-                                    KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm);
-                            break;
-                        case KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            MAX_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultMaxTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            MMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultMmsTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            SMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultSmsTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS:
-                            NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_NOTIFICATION_ALLOWLIST_DURATION_MS,
-                                    mDefaultNotificationAllowlistDurationMs);
-                            break;
-                        case KEY_WAIT_FOR_UNLOCK:
-                            WAIT_FOR_UNLOCK = properties.getBoolean(
-                                    KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock);
-                            break;
-                        case KEY_USE_WINDOW_ALARMS:
-                            USE_WINDOW_ALARMS = properties.getBoolean(
-                                    KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
-                            break;
-                        case KEY_USE_MODE_MANAGER:
-                            USE_MODE_MANAGER = properties.getBoolean(
-                                    KEY_USE_MODE_MANAGER, mDefaultUseModeManager);
-                            break;
-                        default:
-                            Slog.e(TAG, "Unknown configuration key: " + name);
-                            break;
-                    }
-                }
+                mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
+                updateConstantsLocked();
             }
         }
 
+        private void updateConstantsLocked() {
+            if (mSmallBatteryDevice) return;
+            FLEX_TIME_SHORT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort);
+
+            LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
+                    mDefaultLightIdleAfterInactiveTimeout);
+
+            LIGHT_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout);
+
+            LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX,
+                    mDefaultLightIdleTimeoutInitialFlex);
+
+            LIGHT_IDLE_TIMEOUT_MAX_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX,
+                    mDefaultLightIdleTimeoutMaxFlex);
+
+            LIGHT_IDLE_FACTOR = Math.max(1, mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor));
+
+            LIGHT_IDLE_INCREASE_LINEARLY = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_LIGHT_IDLE_INCREASE_LINEARLY,
+                    mDefaultLightIdleIncreaseLinearly);
+
+            LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS,
+                    mDefaultLightIdleLinearIncreaseFactorMs);
+
+            LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS,
+                    mDefaultLightIdleFlexLinearIncreaseFactorMs);
+
+            LIGHT_MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout);
+
+            LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
+                    mDefaultLightIdleMaintenanceMinBudget);
+
+            LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
+                    mDefaultLightIdleMaintenanceMaxBudget);
+
+            MIN_LIGHT_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_LIGHT_MAINTENANCE_TIME,
+                    mDefaultMinLightMaintenanceTime);
+
+            MIN_DEEP_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_DEEP_MAINTENANCE_TIME,
+                    mDefaultMinDeepMaintenanceTime);
+
+            final long defaultInactiveTimeout = mSmallBatteryDevice
+                    ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY
+                    : mDefaultInactiveTimeout;
+            INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout);
+
+            SENSING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_SENSING_TIMEOUT, mDefaultSensingTimeout);
+
+            LOCATING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout);
+
+            LOCATION_ACCURACY = mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy);
+
+            MOTION_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout);
+
+            MOTION_INACTIVE_TIMEOUT_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MOTION_INACTIVE_TIMEOUT_FLEX,
+                    mDefaultMotionInactiveTimeoutFlex);
+
+            final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice
+                    ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY
+                    : mDefaultIdleAfterInactiveTimeout;
+            IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
+                    defaultIdleAfterInactiveTimeout);
+
+            IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout);
+
+            MAX_IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout);
+
+            IDLE_PENDING_FACTOR = mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor);
+
+            QUICK_DOZE_DELAY_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout);
+
+            IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_TIMEOUT, mDefaultIdleTimeout);
+
+            MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout);
+
+            IDLE_FACTOR = mUserSettingDeviceConfigMediator.getFloat(KEY_IDLE_FACTOR,
+                    mDefaultIdleFactor);
+
+            MIN_TIME_TO_ALARM = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm);
+
+            MAX_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultMaxTempAppAllowlistDurationMs);
+
+            MMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultMmsTempAppAllowlistDurationMs);
+
+            SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultSmsTempAppAllowlistDurationMs);
+
+            NOTIFICATION_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_NOTIFICATION_ALLOWLIST_DURATION_MS,
+                    mDefaultNotificationAllowlistDurationMs);
+
+            WAIT_FOR_UNLOCK = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock);
+
+            USE_WINDOW_ALARMS = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
+
+            USE_MODE_MANAGER = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_USE_MODE_MANAGER, mDefaultUseModeManager);
+        }
+
         void dump(PrintWriter pw) {
             pw.println("  Settings:");
 
@@ -2490,9 +2485,10 @@
             return mConnectivityManager;
         }
 
-        Constants getConstants(DeviceIdleController controller) {
+        Constants getConstants(DeviceIdleController controller, Handler handler,
+                ContentResolver resolver) {
             if (mConstants == null) {
-                mConstants = controller.new Constants();
+                mConstants = controller.new Constants(handler, resolver);
             }
             return mConstants;
         }
@@ -2650,7 +2646,7 @@
                 }
             }
 
-            mConstants = mInjector.getConstants(this);
+            mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver());
 
             readConfigFileLocked();
             updateWhitelistAppIdsLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index a720baf..6f8014f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -22,7 +22,7 @@
 import static android.app.AlarmManager.RTC_WAKEUP;
 
 import static com.android.server.alarm.AlarmManagerService.PRIORITY_NORMAL;
-import static com.android.server.alarm.AlarmManagerService.clampPositive;
+import static com.android.server.alarm.AlarmManagerService.addClampPositive;
 
 import android.app.AlarmManager;
 import android.app.IAlarmListener;
@@ -148,7 +148,7 @@
         mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed;
         mWhenElapsed = requestedWhenElapsed;
         this.windowLength = windowLength;
-        mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength);
+        mMaxWhenElapsed = addClampPositive(requestedWhenElapsed, windowLength);
         repeatInterval = interval;
         operation = op;
         listener = rec;
@@ -244,8 +244,8 @@
 
         final long oldMaxWhenElapsed = mMaxWhenElapsed;
         // windowLength should always be >= 0 here.
-        final long maxRequestedElapsed = clampPositive(
-                mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength);
+        final long maxRequestedElapsed = addClampPositive(
+                mPolicyWhenElapsed[REQUESTER_POLICY_INDEX], windowLength);
         mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed);
 
         return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 384a480..1bd8da82 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1417,15 +1417,15 @@
         if (futurity < MIN_FUZZABLE_INTERVAL) {
             futurity = 0;
         }
-        long maxElapsed = triggerAtTime + (long) (0.75 * futurity);
+        long maxElapsed = addClampPositive(triggerAtTime, (long) (0.75 * futurity));
         // For non-repeating alarms, window is capped at a maximum of one hour from the requested
         // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable.
         // In practice, the delivery window should generally be much smaller than that
         // when the device is not idling.
         if (interval == 0) {
-            maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR);
+            maxElapsed = Math.min(maxElapsed, addClampPositive(triggerAtTime, INTERVAL_HOUR));
         }
-        return clampPositive(maxElapsed);
+        return maxElapsed;
     }
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
@@ -1512,6 +1512,18 @@
         return (val >= 0) ? val : Long.MAX_VALUE;
     }
 
+    static long addClampPositive(long val1, long val2) {
+        long val = val1 + val2;
+        if (val >= 0) {
+            return val;
+        } else if (val1 >= 0 && val2 >= 0) {
+            /* Both are +ve, so overflow happened. */
+            return Long.MAX_VALUE;
+        } else {
+            return 0;
+        }
+    }
+
     /**
      * Sends alarms that were blocked due to user applied background restrictions - either because
      * the user lifted those or the uid came to foreground.
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 721a8bd..6449edc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -557,6 +557,11 @@
                 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                         traceTag, getId());
             }
+            if (job.getAppTraceTag() != null) {
+                // Use the job's ID to distinguish traces since the ID will be unique per app.
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler",
+                        job.getAppTraceTag(), job.getJobId());
+            }
             try {
                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
             } catch (RemoteException e) {
@@ -1616,6 +1621,10 @@
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
         }
+        if (completedJob.getAppTraceTag() != null) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler",
+                    completedJob.getJobId());
+        }
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
                     loggingInternalStopReason);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d466f0d..afcbdda 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -510,6 +510,8 @@
     private static final String XML_TAG_ONEOFF = "one-off";
     private static final String XML_TAG_EXTRAS = "extras";
     private static final String XML_TAG_JOB_WORK_ITEM = "job-work-item";
+    private static final String XML_TAG_DEBUG_INFO = "debug-info";
+    private static final String XML_TAG_DEBUG_TAG = "debug-tag";
 
     private void migrateJobFilesAsync() {
         synchronized (mLock) {
@@ -805,6 +807,7 @@
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getJob().getExtras(), out);
                     writeJobWorkItemsToXml(out, jobStatus);
+                    writeDebugInfoToXml(out, jobStatus);
                     out.endTag(null, XML_TAG_JOB);
 
                     numJobs++;
@@ -991,6 +994,26 @@
             }
         }
 
+        private void writeDebugInfoToXml(@NonNull TypedXmlSerializer out,
+                @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException {
+            final ArraySet<String> debugTags = jobStatus.getJob().getDebugTagsArraySet();
+            final int numTags = debugTags.size();
+            final String traceTag = jobStatus.getJob().getTraceTag();
+            if (traceTag == null && numTags == 0) {
+                return;
+            }
+            out.startTag(null, XML_TAG_DEBUG_INFO);
+            if (traceTag != null) {
+                out.attribute(null, "trace-tag", traceTag);
+            }
+            for (int i = 0; i < numTags; ++i) {
+                out.startTag(null, XML_TAG_DEBUG_TAG);
+                out.attribute(null, "tag", debugTags.valueAt(i));
+                out.endTag(null, XML_TAG_DEBUG_TAG);
+            }
+            out.endTag(null, XML_TAG_DEBUG_INFO);
+        }
+
         private void writeJobWorkItemsToXml(@NonNull TypedXmlSerializer out,
                 @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException {
             // Write executing first since they're technically at the front of the queue.
@@ -1449,6 +1472,18 @@
                 jobWorkItems = readJobWorkItemsFromXml(parser);
             }
 
+            if (eventType == XmlPullParser.START_TAG
+                    && XML_TAG_DEBUG_INFO.equals(parser.getName())) {
+                try {
+                    jobBuilder.setTraceTag(parser.getAttributeValue(null, "trace-tag"));
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Invalid trace tag persisted to disk", e);
+                }
+                parser.next();
+                jobBuilder.addDebugTags(readDebugTagsFromXml(parser));
+                eventType = parser.nextTag(); // Consume </debug-info>
+            }
+
             final JobInfo builtJob;
             try {
                 // Don't perform prefetch-deadline check here. Apps targeting S- shouldn't have
@@ -1721,6 +1756,33 @@
                 return null;
             }
         }
+
+        @NonNull
+        private Set<String> readDebugTagsFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            Set<String> debugTags = new ArraySet<>();
+
+            for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT;
+                    eventType = parser.next()) {
+                final String tagName = parser.getName();
+                if (!XML_TAG_DEBUG_TAG.equals(tagName)) {
+                    // We're no longer operating with debug tags.
+                    break;
+                }
+                if (debugTags.size() < JobInfo.MAX_NUM_DEBUG_TAGS) {
+                    final String debugTag;
+                    try {
+                        debugTag = JobInfo.validateDebugTag(parser.getAttributeValue(null, "tag"));
+                    } catch (Exception e) {
+                        Slog.wtf(TAG, "Invalid debug tag persisted to disk", e);
+                        continue;
+                    }
+                    debugTags.add(debugTag);
+                }
+            }
+
+            return debugTags;
+        }
     }
 
     /** Set of all tracked jobs. */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 0cf0cc5..e06006f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -640,22 +640,27 @@
         if (mCcConfig.mShouldReprocessNetworkCapabilities
                 || (mFlexibilityController.isEnabled() != mCcConfig.mFlexIsEnabled)) {
             AppSchedulingModuleThread.getHandler().post(() -> {
-                boolean shouldUpdateJobs = false;
-                for (int i = 0; i < mAvailableNetworks.size(); ++i) {
-                    CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i);
-                    if (metadata == null || metadata.networkCapabilities == null) {
-                        continue;
+                boolean flexAffinitiesChanged = false;
+                boolean flexAffinitiesSatisfied = false;
+                synchronized (mLock) {
+                    for (int i = 0; i < mAvailableNetworks.size(); ++i) {
+                        CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i);
+                        if (metadata == null) {
+                            continue;
+                        }
+                        if (updateTransportAffinitySatisfaction(metadata)) {
+                            // Something changed. Update jobs.
+                            flexAffinitiesChanged = true;
+                        }
+                        flexAffinitiesSatisfied |= metadata.satisfiesTransportAffinities;
                     }
-                    boolean satisfies = satisfiesTransportAffinities(metadata.networkCapabilities);
-                    if (metadata.satisfiesTransportAffinities != satisfies) {
-                        metadata.satisfiesTransportAffinities = satisfies;
-                        // Something changed. Update jobs.
-                        shouldUpdateJobs = true;
+                    if (flexAffinitiesChanged) {
+                        mFlexibilityController.setConstraintSatisfied(
+                                JobStatus.CONSTRAINT_CONNECTIVITY,
+                                flexAffinitiesSatisfied, sElapsedRealtimeClock.millis());
+                        updateAllTrackedJobsLocked(false);
                     }
                 }
-                if (shouldUpdateJobs) {
-                    updateAllTrackedJobsLocked(false);
-                }
             });
         }
     }
@@ -1059,6 +1064,22 @@
         return false;
     }
 
+    /**
+     * Updates {@link CachedNetworkMetadata#satisfiesTransportAffinities} in the given
+     * {@link CachedNetworkMetadata} object.
+     * @return true if the satisfaction changed
+     */
+    private boolean updateTransportAffinitySatisfaction(
+            @NonNull CachedNetworkMetadata cachedNetworkMetadata) {
+        final boolean satisfiesAffinities =
+                satisfiesTransportAffinities(cachedNetworkMetadata.networkCapabilities);
+        if (cachedNetworkMetadata.satisfiesTransportAffinities != satisfiesAffinities) {
+            cachedNetworkMetadata.satisfiesTransportAffinities = satisfiesAffinities;
+            return true;
+        }
+        return false;
+    }
+
     private boolean satisfiesTransportAffinities(@Nullable NetworkCapabilities capabilities) {
         if (!mFlexibilityController.isEnabled()) {
             return true;
@@ -1552,7 +1573,9 @@
                     }
                 }
                 cnm.networkCapabilities = capabilities;
-                cnm.satisfiesTransportAffinities = satisfiesTransportAffinities(capabilities);
+                if (updateTransportAffinitySatisfaction(cnm)) {
+                    maybeUpdateFlexConstraintLocked(cnm);
+                }
                 maybeRegisterSignalStrengthCallbackLocked(capabilities);
                 updateTrackedJobsLocked(-1, network);
                 postAdjustCallbacks();
@@ -1566,8 +1589,13 @@
             }
             synchronized (mLock) {
                 final CachedNetworkMetadata cnm = mAvailableNetworks.remove(network);
-                if (cnm != null && cnm.networkCapabilities != null) {
-                    maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities);
+                if (cnm != null) {
+                    if (cnm.networkCapabilities != null) {
+                        maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities);
+                    }
+                    if (cnm.satisfiesTransportAffinities) {
+                        maybeUpdateFlexConstraintLocked(null);
+                    }
                 }
                 for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
                     UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
@@ -1639,6 +1667,37 @@
                 }
             }
         }
+
+        /**
+         * Maybe call {@link FlexibilityController#setConstraintSatisfied(int, boolean, long)}
+         * if the network affinity state has changed.
+         */
+        @GuardedBy("mLock")
+        private void maybeUpdateFlexConstraintLocked(
+                @Nullable CachedNetworkMetadata cachedNetworkMetadata) {
+            if (cachedNetworkMetadata != null
+                    && cachedNetworkMetadata.satisfiesTransportAffinities) {
+                mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY,
+                        true, sElapsedRealtimeClock.millis());
+            } else {
+                // This network doesn't satisfy transport affinities. Check if any other
+                // available networks do satisfy the affinities before saying that the
+                // transport affinity is no longer satisfied for flex.
+                boolean isTransportAffinitySatisfied = false;
+                for (int i = mAvailableNetworks.size() - 1; i >= 0; --i) {
+                    final CachedNetworkMetadata cnm = mAvailableNetworks.valueAt(i);
+                    if (cnm != null && cnm.satisfiesTransportAffinities) {
+                        isTransportAffinitySatisfied = true;
+                        break;
+                    }
+                }
+                if (!isTransportAffinitySatisfied) {
+                    mFlexibilityController.setConstraintSatisfied(
+                            JobStatus.CONSTRAINT_CONNECTIVITY, false,
+                            sElapsedRealtimeClock.millis());
+                }
+            }
+        }
     };
 
     private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 70f9a52..fed3c42 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -43,6 +43,8 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -68,11 +70,6 @@
             | CONSTRAINT_CHARGING
             | CONSTRAINT_IDLE;
 
-    /** List of flexible constraints a job can opt into. */
-    static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
-            | CONSTRAINT_CHARGING
-            | CONSTRAINT_IDLE;
-
     /** List of all job flexible constraints whose satisfaction is job specific. */
     private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
 
@@ -83,9 +80,6 @@
     private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
 
-    static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS =
-            Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS);
-
     static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
 
@@ -103,6 +97,9 @@
     private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
     private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
 
+    private long mUnseenConstraintGracePeriodMs =
+            FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
@@ -132,6 +129,9 @@
     @GuardedBy("mLock")
     int mSatisfiedFlexibleConstraints;
 
+    @GuardedBy("mLock")
+    private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray();
+
     @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
@@ -258,25 +258,68 @@
     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
         return !mFlexibilityEnabled
                 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
-                || getNumSatisfiedRequiredConstraintsLocked(js)
-                        >= js.getNumRequiredFlexibleConstraints()
+                || hasEnoughSatisfiedConstraintsLocked(js)
                 || mService.isCurrentlyRunningLocked(js);
     }
 
+    /**
+     * Returns whether there are enough constraints satisfied to allow running the job from flex's
+     * perspective. This takes into account unseen constraint combinations and expectations around
+     * whether additional constraints can ever be satisfied.
+     */
     @VisibleForTesting
     @GuardedBy("mLock")
-    int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
-        return Integer.bitCount(mSatisfiedFlexibleConstraints)
-                // Connectivity is job-specific, so must be handled separately.
-                + (js.canApplyTransportAffinities()
-                        && js.areTransportAffinitiesSatisfied() ? 1 : 0);
+    boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) {
+        final int satisfiedConstraints = mSatisfiedFlexibleConstraints
+                & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+                        | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0));
+        final int numSatisfied = Integer.bitCount(satisfiedConstraints);
+        if (numSatisfied >= js.getNumRequiredFlexibleConstraints()) {
+            return true;
+        }
+        // We don't yet have the full number of required flex constraints. See if we should expect
+        // to be able to reach it. If not, then there's no point waiting anymore.
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (nowElapsed < mUnseenConstraintGracePeriodMs) {
+            // Too soon after boot. Not enough time to start predicting. Wait longer.
+            return false;
+        }
+
+        // The intention is to not force jobs to wait for constraint combinations that have never
+        // been seen together in a while. The job may still be allowed to wait for other constraint
+        // combinations. Thus, the logic is:
+        // If all the constraint combinations that have a count higher than the current satisfied
+        // count have not been seen recently enough, then assume they won't be seen anytime soon,
+        // so don't force the job to wait longer. If any combinations with a higher count have been
+        // seen recently, then the job can potentially wait for those combinations.
+        final int irrelevantConstraints = ~(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+                | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0));
+        for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) {
+            final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
+            if ((constraints & irrelevantConstraints) != 0) {
+                // Ignore combinations that couldn't satisfy this job's needs.
+                continue;
+            }
+            final long lastSeenElapsed = mLastSeenConstraintTimesElapsed.valueAt(i);
+            final boolean seenRecently =
+                    nowElapsed - lastSeenElapsed <= mUnseenConstraintGracePeriodMs;
+            if (Integer.bitCount(constraints) > numSatisfied && seenRecently) {
+                // We've seen a set of constraints with a higher count than what is currently
+                // satisfied recently enough, which means we can expect to see it again at some
+                // point. Keep waiting for now.
+                return false;
+            }
+        }
+
+        // We haven't seen any constraint set with more satisfied than the current satisfied count.
+        // There's no reason to expect additional constraints to be satisfied. Let the job run.
+        return true;
     }
 
     /**
      * Sets the controller's constraint to a given state.
      * Changes flexibility constraint satisfaction for affected jobs.
      */
-    @VisibleForTesting
     void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) {
         synchronized (mLock) {
             final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -286,14 +329,34 @@
 
             if (DEBUG) {
                 Slog.d(TAG, "setConstraintSatisfied: "
-                       + " constraint: " + constraint + " state: " + state);
+                        + " constraint: " + constraint + " state: " + state);
+            }
+
+            // Mark now as the last time we saw this set of constraints.
+            mLastSeenConstraintTimesElapsed.put(mSatisfiedFlexibleConstraints, nowElapsed);
+            if (!state) {
+                // Mark now as the last time we saw this particular constraint.
+                // (Good for logging/dump purposes).
+                mLastSeenConstraintTimesElapsed.put(constraint, nowElapsed);
             }
 
             mSatisfiedFlexibleConstraints =
                     (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0);
-            // Push the job update to the handler to avoid blocking other controllers and
-            // potentially batch back-to-back controller state updates together.
-            mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
+
+            if ((JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS & constraint) != 0) {
+                // Job-specific constraint --> don't need to proceed with logic below that
+                // works with system-wide constraints.
+                return;
+            }
+
+            if (mFlexibilityEnabled) {
+                // Only attempt to update jobs if the flex logic is enabled. Otherwise, the status
+                // of the jobs won't change, so all the work will be a waste.
+
+                // Push the job update to the handler to avoid blocking other controllers and
+                // potentially batch back-to-back controller state updates together.
+                mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
+            }
         }
     }
 
@@ -543,7 +606,6 @@
                     if (!predicate.test(js)) {
                         continue;
                     }
-                    pw.print("#");
                     js.printUniqueId(pw);
                     pw.print(" from ");
                     UserHandle.formatUid(pw, js.getSourceUid());
@@ -645,7 +707,7 @@
                         final long nowElapsed = sElapsedRealtimeClock.millis();
                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
 
-                        for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) {
+                        for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) {
                             final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                                     .getJobsByNumRequiredConstraints(o);
 
@@ -687,6 +749,8 @@
                 FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
         static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
                 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
+        static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
+                FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms";
 
         static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
         @VisibleForTesting
@@ -698,6 +762,8 @@
         final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
         private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
         private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
+        @VisibleForTesting
+        static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
 
         /**
          * If false the controller will not track new jobs
@@ -717,6 +783,11 @@
         public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
         /** The max deadline for rescheduled jobs. */
         public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
+        /**
+         * How long to wait after last seeing a constraint combination before no longer waiting for
+         * it in order to run jobs.
+         */
+        public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
 
         @GuardedBy("mLock")
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@@ -780,6 +851,14 @@
                         mShouldReevaluateConstraints = true;
                     }
                     break;
+                case KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS:
+                    UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
+                            properties.getLong(key, DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS);
+                    if (mUnseenConstraintGracePeriodMs != UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) {
+                        mUnseenConstraintGracePeriodMs = UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
                 case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
                     String dropPercentString = properties.getString(key, "");
                     PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
@@ -834,6 +913,8 @@
                     PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println();
             pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
             pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
+            pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS)
+                    .println();
 
             pw.decreaseIndent();
         }
@@ -854,12 +935,34 @@
     @Override
     @GuardedBy("mLock")
     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-        pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints));
-        pw.print("Satisfied Flexible Constraints: ");
+        pw.print("Satisfied Flexible Constraints:");
         JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints);
         pw.println();
         pw.println();
 
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        pw.println("Time since constraint combos last seen:");
+        pw.increaseIndent();
+        for (int i = 0; i < mLastSeenConstraintTimesElapsed.size(); ++i) {
+            final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
+            if (constraints == mSatisfiedFlexibleConstraints) {
+                pw.print("0ms");
+            } else {
+                TimeUtils.formatDuration(
+                        mLastSeenConstraintTimesElapsed.valueAt(i), nowElapsed, pw);
+            }
+            pw.print(":");
+            if (constraints != 0) {
+                // dumpConstraints prepends with a space, so no need to add a space after the :
+                JobStatus.dumpConstraints(pw, constraints);
+            } else {
+                pw.print(" none");
+            }
+            pw.println();
+        }
+        pw.decreaseIndent();
+
+        pw.println();
         mFlexibilityTracker.dump(pw, predicate);
         pw.println();
         mFlexibilityAlarmQueue.dump(pw);
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 d6ada4c..13bea6b 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
@@ -598,7 +598,6 @@
             long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
             int internalFlags,
             int dynamicConstraints) {
-        this.job = job;
         this.callingUid = callingUid;
         this.standbyBucket = standbyBucket;
         mNamespace = namespace;
@@ -626,6 +625,22 @@
             this.sourceTag = tag;
         }
 
+        // This needs to be done before setting the field variable.
+        if (job.getRequiredNetwork() != null) {
+            // Later, when we check if a given network satisfies the required
+            // network, we need to know the UID that is requesting it, so push
+            // the source UID into place.
+            final JobInfo.Builder builder = new JobInfo.Builder(job);
+            builder.setRequiredNetwork(new NetworkRequest.Builder(job.getRequiredNetwork())
+                    .setUids(Collections.singleton(new Range<>(this.sourceUid, this.sourceUid)))
+                    .build());
+            // Don't perform validation checks at this point since we've already passed the
+            // initial validation check.
+            job = builder.build(false, false);
+        }
+
+        this.job = job;
+
         final String bnNamespace = namespace == null ? "" :  "@" + namespace + "@";
         this.batteryName = this.sourceTag != null
                 ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
@@ -708,21 +723,6 @@
 
         updateNetworkBytesLocked();
 
-        if (job.getRequiredNetwork() != null) {
-            // Later, when we check if a given network satisfies the required
-            // network, we need to know the UID that is requesting it, so push
-            // our source UID into place.
-            final JobInfo.Builder builder = new JobInfo.Builder(job);
-            final NetworkRequest.Builder requestBuilder =
-                    new NetworkRequest.Builder(job.getRequiredNetwork());
-            requestBuilder.setUids(
-                    Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
-            builder.setRequiredNetwork(requestBuilder.build());
-            // Don't perform validation checks at this point since we've already passed the
-            // initial validation check.
-            job = builder.build(false, false);
-        }
-
         updateMediaBackupExemptionStatus();
     }
 
@@ -1054,6 +1054,12 @@
         return mLoggingJobId;
     }
 
+    /** Returns a trace tag using debug information provided by the app. */
+    @Nullable
+    public String getAppTraceTag() {
+        return job.getTraceTag();
+    }
+
     /** Returns whether this job was scheduled by one app on behalf of another. */
     public boolean isProxyJob() {
         return mIsProxyJob;
@@ -2763,6 +2769,15 @@
                 pw.println("Has late constraint");
             }
 
+            if (job.getTraceTag() != null) {
+                pw.print("Trace tag: ");
+                pw.println(job.getTraceTag());
+            }
+            if (job.getDebugTags().size() > 0) {
+                pw.print("Debug tags: ");
+                pw.println(job.getDebugTags());
+            }
+
             pw.decreaseIndent();
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index d2150b8..8381d1a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -109,7 +109,6 @@
 import android.content.ContentResolver;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -154,7 +153,6 @@
     private long mMinSatiatedConsumptionLimit;
     private long mMaxSatiatedConsumptionLimit;
 
-    private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -241,35 +239,36 @@
         mRewards.clear();
 
         try {
-            mParser.setString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, "Global setting key incorrect: ", e);
         }
 
-        mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceOther = getConstantAsCake(
             KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
-        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(
                 KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                 DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
                 mMinSatiatedBalanceOther);
-        mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceExempted = getConstantAsCake(
                 KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
                 DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mMinSatiatedBalanceHeadlessSystemApp);
-        mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
+        mMaxSatiatedBalance = getConstantAsCake(
             KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
-        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMinSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
                 arcToCake(1));
-        mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mInitialSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mMinSatiatedConsumptionLimit);
-        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
                 mInitialSatiatedConsumptionLimit);
 
-        final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES);
 
@@ -279,49 +278,49 @@
         // run out of credits, and not when the system has run out of stock.
         mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES),
                         exactAllowWhileIdleWakeupBasePrice,
                         /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_EXACT,
                 new Action(ACTION_ALARM_WAKEUP_EXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
         final long inexactAllowWhileIdleWakeupBasePrice =
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE,
                         DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES);
 
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES),
                         inexactAllowWhileIdleWakeupBasePrice,
                         /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
-        final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES);
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES),
                         exactAllowWhileIdleNonWakeupBasePrice,
@@ -329,18 +328,18 @@
 
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
-        final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES);
-        final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(mParser, properties,
+        final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES);
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE,
@@ -350,72 +349,72 @@
 
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT,
                 new Action(ACTION_ALARM_NONWAKEUP_INEXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES)));
         mActions.put(ACTION_ALARM_CLOCK,
                 new Action(ACTION_ALARM_CLOCK,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
         mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_INSTANT,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_ONGOING,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_MAX,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_MAX,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_MAX,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES)));
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index a4043dd..61096b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -33,9 +33,9 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -197,10 +197,17 @@
     }
 
     protected final InternalResourceService mIrs;
+    protected final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator;
     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
 
     EconomicPolicy(@NonNull InternalResourceService irs) {
         mIrs = irs;
+        // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
+        // config can cause issues since the scales may be different, so use one or the other.
+        // If user settings exist, then just stick with the Settings constants, even if there
+        // are invalid values.
+        mUserSettingDeviceConfigMediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(',');
         for (int mId : getCostModifiers()) {
             initModifier(mId, irs);
         }
@@ -464,28 +471,14 @@
         return "UNKNOWN_REWARD:" + Integer.toHexString(eventId);
     }
 
-    protected long getConstantAsCake(@NonNull KeyValueListParser parser,
-            @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) {
-        return getConstantAsCake(parser, properties, key, defaultValCake, 0);
+    protected long getConstantAsCake(String key, long defaultValCake) {
+        return getConstantAsCake(key, defaultValCake, 0);
     }
 
-    protected long getConstantAsCake(@NonNull KeyValueListParser parser,
-            @Nullable DeviceConfig.Properties properties, String key, long defaultValCake,
-            long minValCake) {
-        // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
-        // config can cause issues since the scales may be different, so use one or the other.
-        if (parser.size() > 0) {
-            // User settings take precedence. Just stick with the Settings constants, even if there
-            // are invalid values. It's not worth the time to evaluate all the key/value pairs to
-            // make sure there are valid ones before deciding.
-            return Math.max(minValCake,
-                parseCreditValue(parser.getString(key, null), defaultValCake));
-        }
-        if (properties != null) {
-            return Math.max(minValCake,
-                parseCreditValue(properties.getString(key, null), defaultValCake));
-        }
-        return Math.max(minValCake, defaultValCake);
+    protected long getConstantAsCake(String key, long defaultValCake, long minValCake) {
+        return Math.max(minValCake,
+                parseCreditValue(
+                        mUserSettingDeviceConfigMediator.getString(key, null), defaultValCake));
     }
 
     @VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 91a291f..69e5736 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -127,7 +127,6 @@
 import android.content.ContentResolver;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -168,7 +167,6 @@
     private long mMinSatiatedConsumptionLimit;
     private long mMaxSatiatedConsumptionLimit;
 
-    private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -274,176 +272,177 @@
         mRewards.clear();
 
         try {
-            mParser.setString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, "Global setting key incorrect: ", e);
         }
 
-        mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceOther = getConstantAsCake(
             KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
-        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
                 mMinSatiatedBalanceOther);
-        mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceExempted = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mMinSatiatedBalanceHeadlessSystemApp);
-        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
-        mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
+        mMaxSatiatedBalance = getConstantAsCake(
             KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
-        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMinSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
                 arcToCake(1));
-        mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mInitialSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mMinSatiatedConsumptionLimit);
-        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
                 mInitialSatiatedConsumptionLimit);
 
         mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_START_CTP,
                         DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_START_CTP,
                         DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_START_CTP,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_START_CTP,
                         DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_START_CTP,
                         DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP,
                         DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES)));
 
         mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_INSTANT,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_ONGOING,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_MAX,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_MAX,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_MAX,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_APP_INSTALL,
                 new Reward(REWARD_APP_INSTALL,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_INSTANT,
                                 DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_ONGOING,
                                 DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_MAX,
                                 DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES)));
     }
diff --git a/api/Android.bp b/api/Android.bp
index 4d56b37..2b1cfcb 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -80,7 +80,9 @@
         "framework-location",
         "framework-media",
         "framework-mediaprovider",
+        "framework-nfc",
         "framework-ondevicepersonalization",
+        "framework-pdf",
         "framework-permission",
         "framework-permission-s",
         "framework-scheduling",
@@ -383,7 +385,10 @@
     stub_only_libs: ["framework-protos"],
     impl_only_libs: ["framework-minus-apex-headers"], // the framework, including hidden API
     impl_library_visibility: ["//frameworks/base"],
-    defaults_visibility: ["//frameworks/base/location"],
+    defaults_visibility: [
+        "//frameworks/base/location",
+        "//frameworks/base/nfc",
+    ],
     plugins: ["error_prone_android_framework"],
     errorprone: {
         javacflags: [
@@ -399,3 +404,49 @@
     "ApiDocs.bp",
     "StubLibraries.bp",
 ]
+
+genrule_defaults {
+    name: "flag-api-mapping-generation-defaults",
+    cmd: "$(location extract-flagged-apis) $(in) $(out)",
+    tools: ["extract-flagged-apis"],
+}
+
+genrule {
+    name: "flag-api-mapping-PublicApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-current.txt"],
+    out: ["flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-SystemApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-system-current.txt"],
+    out: ["system_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-ModuleLibApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-module-lib-current.txt"],
+    out: ["module_lib_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+genrule {
+    name: "flag-api-mapping-SystemServerApi",
+    defaults: ["flag-api-mapping-generation-defaults"],
+    srcs: [":frameworks-base-api-system-server-current.txt"],
+    out: ["system_server_flag_api_map.textproto"],
+    dist: {
+        targets: ["droid"],
+    },
+}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index f6f6929..28b2d4b 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -635,6 +635,7 @@
     api_contributions: [
         "framework-virtualization.stubs.source.test.api.contribution",
         "framework-location.stubs.source.test.api.contribution",
+        "framework-nfc.stubs.source.test.api.contribution",
     ],
 }
 
diff --git a/api/api.go b/api/api.go
index 8df6dab..71b1e10 100644
--- a/api/api.go
+++ b/api/api.go
@@ -32,6 +32,7 @@
 const i18n = "i18n.module.public.api"
 const virtualization = "framework-virtualization"
 const location = "framework-location"
+const nfc = "framework-nfc"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
 
@@ -43,7 +44,7 @@
 // APIs.
 // In addition, the modules in this list are allowed to contribute to test APIs
 // stubs.
-var non_updatable_modules = []string{virtualization, location}
+var non_updatable_modules = []string{virtualization, location, nfc}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
diff --git a/api/coverage/tools/Android.bp b/api/coverage/tools/Android.bp
new file mode 100644
index 0000000..3e16912
--- /dev/null
+++ b/api/coverage/tools/Android.bp
@@ -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.
+
+java_binary_host {
+    name: "extract-flagged-apis",
+    srcs: ["ExtractFlaggedApis.kt"],
+    main_class: "android.platform.coverage.ExtractFlaggedApisKt",
+    static_libs: [
+        "metalava-signature-reader",
+        "extract_flagged_apis_proto",
+    ],
+}
+
+java_library_host {
+    name: "extract_flagged_apis_proto",
+    srcs: ["extract_flagged_apis.proto"],
+    static_libs: ["libprotobuf-java-full"],
+    proto: {
+        type: "full",
+    },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
new file mode 100644
index 0000000..9ffb704
--- /dev/null
+++ b/api/coverage/tools/ExtractFlaggedApis.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 android.platform.coverage
+
+import com.android.tools.metalava.model.text.ApiFile
+import java.io.File
+import java.io.FileWriter
+
+/** Usage: extract-flagged-apis <api text file> <output .pb file> */
+fun main(args: Array<String>) {
+    var cb = ApiFile.parseApi(listOf(File(args[0])))
+    val flagToApi = mutableMapOf<String, MutableList<String>>()
+    cb.getPackages()
+        .allClasses()
+        .filter { it.methods().size > 0 }
+        .forEach {
+            for (method in it.methods()) {
+                val flagValue =
+                    method.modifiers
+                        .findAnnotation("android.annotation.FlaggedApi")
+                        ?.findAttribute("value")
+                        ?.value
+                        ?.value()
+                if (flagValue != null && flagValue is String) {
+                    val methodQualifiedName = "${it.qualifiedName()}.${method.name()}"
+                    if (flagToApi.containsKey(flagValue)) {
+                        flagToApi.get(flagValue)?.add(methodQualifiedName)
+                    } else {
+                        flagToApi.put(flagValue, mutableListOf(methodQualifiedName))
+                    }
+                }
+            }
+        }
+    var builder = FlagApiMap.newBuilder()
+    for (flag in flagToApi.keys) {
+        var flaggedApis = FlaggedApis.newBuilder()
+        for (method in flagToApi.get(flag).orEmpty()) {
+            flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method))
+        }
+        builder.putFlagToApi(flag, flaggedApis.build())
+    }
+    val flagApiMap = builder.build()
+    FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/api/coverage/tools/extract_flagged_apis.proto
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to api/coverage/tools/extract_flagged_apis.proto
index 8e55695..a858108 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/api/coverage/tools/extract_flagged_apis.proto
@@ -14,7 +14,21 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+syntax = "proto3";
 
-import org.robolectric.annotation.GraphicsMode;
+package android.platform.coverage;
+
+option java_multiple_files = true;
+
+message FlagApiMap {
+  map<string, FlaggedApis> flag_to_api = 1;
+}
+
+message FlaggedApis {
+  repeated FlaggedApi flagged_api = 1;
+}
+
+message FlaggedApi {
+  string qualified_name = 1;
+}
+
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index a4174ee..7992702 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,17 +1,3 @@
-// b/303477132
-android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
-android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
-android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101]
-android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.AppSearchSession [101]
-android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#isFeatureSupported" in android.app.appsearch.AppSearchSession [101]
-android/app/appsearch/JoinSpec.java:219: lint: Unresolved link/see tag "android.app.appsearch.SearchSpec.RankingStrategy#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE SearchSpec.RankingStrategy#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE" in android.app.appsearch.JoinSpec.Builder [101]
-android/app/appsearch/SearchSpec.java:230: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec [101]
-android/app/appsearch/SearchSpec.java:237: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec [101]
-android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec [101]
-android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-
 // b/303582215
 android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 89776db..820d2b0 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -675,7 +675,11 @@
         ss << "ro.bootanim.set_orientation_" << displayId.value;
         return ss.str();
     }();
-    const auto syspropValue = android::base::GetProperty(syspropName, "ORIENTATION_0");
+    auto syspropValue = android::base::GetProperty(syspropName, "");
+    if (syspropValue == "") {
+        syspropValue = android::base::GetProperty("ro.bootanim.set_orientation_logical_0", "");
+    }
+
     if (syspropValue == "ORIENTATION_90") {
         return ui::ROTATION_90;
     } else if (syspropValue == "ORIENTATION_180") {
diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp
index 2232345..d645d06 100644
--- a/cmds/gpu_counter_producer/Android.bp
+++ b/cmds/gpu_counter_producer/Android.bp
@@ -19,6 +19,4 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
-
-    soc_specific: true,
 }
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 2d23533..917529e 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
+#include <getopt.h>
 
 #include <linux/fb.h>
 #include <sys/ioctl.h>
@@ -32,6 +33,7 @@
 
 #include <ftl/concat.h>
 #include <ftl/optional.h>
+#include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
@@ -48,14 +50,17 @@
 #define COLORSPACE_DISPLAY_P3 2
 
 void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) {
-    fprintf(stderr,
-            "usage: %s [-hp] [-d display-id] [FILENAME]\n"
-            "   -h: this message\n"
-            "   -p: save the file as a png.\n"
-            "   -d: specify the display ID to capture%s\n"
-            "       see \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"
-            "If FILENAME ends with .png it will be saved as a png.\n"
-            "If FILENAME is not given, the results will be printed to stdout.\n",
+    fprintf(stderr, R"(
+usage: %s [-hp] [-d display-id] [FILENAME]
+   -h: this message
+   -p: save the file as a png.
+   -d: specify the display ID to capture%s
+       see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
+   --hint-for-seamless If set will use the hintForSeamless path in SF
+
+If FILENAME ends with .png it will be saved as a png.
+If FILENAME is not given, the results will be printed to stdout.
+)",
             pname,
             displayIdOpt
                     .transform([](DisplayId id) {
@@ -65,6 +70,21 @@
                     .c_str());
 }
 
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+    Reserved = 255,
+    HintForSeamless,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+        {"png", no_argument, nullptr, 'p'},
+        {"help", no_argument, nullptr, 'h'},
+        {"hint-for-seamless", no_argument, nullptr, LongOpts::HintForSeamless},
+        {0, 0, 0, 0}};
+
 static int32_t flinger2bitmapFormat(PixelFormat f)
 {
     switch (f) {
@@ -134,10 +154,11 @@
         return 1;
     }
     std::optional<DisplayId> displayIdOpt;
+    gui::CaptureArgs captureArgs;
     const char* pname = argv[0];
     bool png = false;
     int c;
-    while ((c = getopt(argc, argv, "phd:")) != -1) {
+    while ((c = getopt_long(argc, argv, "phd:", LONG_OPTIONS, nullptr)) != -1) {
         switch (c) {
             case 'p':
                 png = true;
@@ -165,6 +186,9 @@
                 }
                 usage(pname, displayIdOpt);
                 return 1;
+            case LongOpts::HintForSeamless:
+                captureArgs.hintForSeamlessTransition = true;
+                break;
         }
     }
 
@@ -215,7 +239,7 @@
     ProcessState::self()->startThreadPool();
 
     sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-    ScreenshotClient::captureDisplay(*displayIdOpt, captureListener);
+    ScreenshotClient::captureDisplay(*displayIdOpt, captureArgs, captureListener);
 
     ScreenCaptureResults captureResults = captureListener->waitForResults();
     if (!captureResults.fenceResult.ok()) {
diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp
index 4b08d96..da497dc 100644
--- a/cmds/uinput/Android.bp
+++ b/cmds/uinput/Android.bp
@@ -22,7 +22,7 @@
     name: "uinput",
     wrapper: "uinput.sh",
     srcs: [
-        "**/*.java",
+        "src/**/*.java",
         ":uinputcommand_aidl",
     ],
     required: ["libuinputcommand_jni"],
diff --git a/cmds/uinput/TEST_MAPPING b/cmds/uinput/TEST_MAPPING
new file mode 100644
index 0000000..e7d619c
--- /dev/null
+++ b/cmds/uinput/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/native/services/inputflinger"
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "UinputTests"
+    }
+  ]
+}
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index 7659054..a78a465 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -96,9 +96,9 @@
     return env;
 }
 
-std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, int32_t vid,
-                                                 int32_t pid, uint16_t bus, uint32_t ffEffectsMax,
-                                                 const char* port,
+std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, int32_t vendorId,
+                                                 int32_t productId, int32_t versionId, uint16_t bus,
+                                                 uint32_t ffEffectsMax, const char* port,
                                                  std::unique_ptr<DeviceCallback> callback) {
     android::base::unique_fd fd(::open(UINPUT_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC));
     if (!fd.ok()) {
@@ -118,8 +118,9 @@
     strlcpy(setupDescriptor.name, name, UINPUT_MAX_NAME_SIZE);
     setupDescriptor.id.version = 1;
     setupDescriptor.id.bustype = bus;
-    setupDescriptor.id.vendor = vid;
-    setupDescriptor.id.product = pid;
+    setupDescriptor.id.vendor = vendorId;
+    setupDescriptor.id.product = productId;
+    setupDescriptor.id.version = versionId;
     setupDescriptor.ff_effects_max = ffEffectsMax;
 
     // Request device configuration.
@@ -242,9 +243,9 @@
     return data;
 }
 
-static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
-                              jint pid, jint bus, jint ffEffectsMax, jstring rawPort,
-                              jobject callback) {
+static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id,
+                              jint vendorId, jint productId, jint versionId, jint bus,
+                              jint ffEffectsMax, jstring rawPort, jobject callback) {
     ScopedUtfChars name(env, rawName);
     if (name.c_str() == nullptr) {
         return 0;
@@ -255,8 +256,8 @@
             std::make_unique<uinput::DeviceCallback>(env, callback);
 
     std::unique_ptr<uinput::UinputDevice> d =
-            uinput::UinputDevice::open(id, name.c_str(), vid, pid, bus, ffEffectsMax, port.c_str(),
-                                       std::move(cb));
+            uinput::UinputDevice::open(id, name.c_str(), vendorId, productId, versionId, bus,
+                                       ffEffectsMax, port.c_str(), std::move(cb));
     return reinterpret_cast<jlong>(d.release());
 }
 
@@ -283,7 +284,10 @@
     std::vector<int32_t> configs = toVector(env, rawConfigs);
     // Configure uinput device, with user specified code and value.
     for (auto& config : configs) {
-        ::ioctl(static_cast<int>(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config);
+        if (::ioctl(static_cast<int>(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config) < 0) {
+            ALOGE("Error configuring device (ioctl %d, value 0x%x): %s", code, config,
+                  strerror(errno));
+        }
     }
 }
 
@@ -323,7 +327,7 @@
 
 static JNINativeMethod sMethods[] = {
         {"nativeOpenUinputDevice",
-         "(Ljava/lang/String;IIIIILjava/lang/String;"
+         "(Ljava/lang/String;IIIIIILjava/lang/String;"
          "Lcom/android/commands/uinput/Device$DeviceCallback;)J",
          reinterpret_cast<void*>(openUinputDevice)},
         {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h
index 6da3d79..9769a75 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.h
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h
@@ -46,9 +46,9 @@
 
 class UinputDevice {
 public:
-    static std::unique_ptr<UinputDevice> open(int32_t id, const char* name, int32_t vid,
-                                              int32_t pid, uint16_t bus, uint32_t ff_effects_max,
-                                              const char* port,
+    static std::unique_ptr<UinputDevice> open(int32_t id, const char* name, int32_t vendorId,
+                                              int32_t productId, int32_t versionId, uint16_t bus,
+                                              uint32_t ff_effects_max, const char* port,
                                               std::unique_ptr<DeviceCallback> callback);
 
     virtual ~UinputDevice();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index ad5e70f..787055c 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -61,8 +61,9 @@
         System.loadLibrary("uinputcommand_jni");
     }
 
-    private static native long nativeOpenUinputDevice(String name, int id, int vid, int pid,
-            int bus, int ffEffectsMax, String port, DeviceCallback callback);
+    private static native long nativeOpenUinputDevice(String name, int id, int vendorId,
+            int productId, int versionId, int bus, int ffEffectsMax, String port,
+            DeviceCallback callback);
     private static native void nativeCloseUinputDevice(long ptr);
     private static native void nativeInjectEvent(long ptr, int type, int code, int value);
     private static native void nativeConfigure(int handle, int code, int[] configs);
@@ -71,7 +72,7 @@
     private static native int nativeGetEvdevEventCodeByLabel(int type, String label);
     private static native int nativeGetEvdevInputPropByLabel(String label);
 
-    public Device(int id, String name, int vid, int pid, int bus,
+    public Device(int id, String name, int vendorId, int productId, int versionId, int bus,
             SparseArray<int[]> configuration, int ffEffectsMax,
             SparseArray<InputAbsInfo> absInfo, String port) {
         mId = id;
@@ -83,19 +84,20 @@
         mOutputStream = System.out;
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = id;
-        args.argi2 = vid;
-        args.argi3 = pid;
-        args.argi4 = bus;
-        args.argi5 = ffEffectsMax;
+        args.argi2 = vendorId;
+        args.argi3 = productId;
+        args.argi4 = versionId;
+        args.argi5 = bus;
+        args.argi6 = ffEffectsMax;
         if (name != null) {
             args.arg1 = name;
         } else {
-            args.arg1 = id + ":" + vid + ":" + pid;
+            args.arg1 = id + ":" + vendorId + ":" + productId;
         }
         if (port != null) {
             args.arg2 = port;
         } else {
-            args.arg2 = "uinput:" + id + ":" + vid + ":" + pid;
+            args.arg2 = "uinput:" + id + ":" + vendorId + ":" + productId;
         }
 
         mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
@@ -160,9 +162,18 @@
             switch (msg.what) {
                 case MSG_OPEN_UINPUT_DEVICE:
                     SomeArgs args = (SomeArgs) msg.obj;
-                    mPtr = nativeOpenUinputDevice((String) args.arg1, args.argi1, args.argi2,
-                            args.argi3, args.argi4, args.argi5, (String) args.arg2,
+                    String name = (String) args.arg1;
+                    mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
+                            args.argi2 /* vendorId */, args.argi3 /* productId */,
+                            args.argi4 /* versionId */, args.argi5 /* bus */,
+                            args.argi6 /* ffEffectsMax */, (String) args.arg2 /* port */,
                             new DeviceCallback());
+                    if (mPtr == 0) {
+                        RuntimeException ex = new RuntimeException(
+                                "Could not create uinput device \"" + name + "\"");
+                        Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
+                        throw ex;
+                    }
                     break;
                 case MSG_INJECT_EVENT:
                     if (mPtr != 0) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
new file mode 100644
index 0000000..7652f24
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.uinput;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * Parser for the <a href="https://gitlab.freedesktop.org/libevdev/evemu">FreeDesktop evemu</a>
+ * event recording format.
+ */
+public class EvemuParser implements EventParser {
+    private static final String TAG = "UinputEvemuParser";
+
+    /**
+     * The device ID to use for all events. Since evemu files only support single-device
+     * recordings, this will always be the same.
+     */
+    private static final int DEVICE_ID = 1;
+    private static final int REGISTRATION_DELAY_MILLIS = 500;
+
+    private static class CommentAwareReader {
+        private final LineNumberReader mReader;
+        private String mPreviousLine;
+        private String mNextLine;
+
+        CommentAwareReader(LineNumberReader in) throws IOException {
+            mReader = in;
+            mNextLine = findNextLine();
+        }
+
+        private @Nullable String findNextLine() throws IOException {
+            String line = "";
+            while (line != null && line.length() == 0) {
+                String unstrippedLine = mReader.readLine();
+                if (unstrippedLine == null) {
+                    // End of file.
+                    return null;
+                }
+                line = stripComments(unstrippedLine);
+            }
+            return line;
+        }
+
+        private static String stripComments(String line) {
+            int index = line.indexOf('#');
+            // 'N:' lines (which contain the name of the input device) do not support trailing
+            // comments, to support recording device names that contain #s.
+            if (index < 0 || line.startsWith("N: ")) {
+                return line;
+            } else {
+                return line.substring(0, index).strip();
+            }
+        }
+
+        /**
+         * Returns the next line of the file that isn't blank when stripped of comments, or
+         * {@code null} if the end of the file is reached. However, it does not advance to the
+         * next line of the file.
+         */
+        public @Nullable String peekLine() {
+            return mNextLine;
+        }
+
+        /** Moves to the next line of the file. */
+        public void advance() throws IOException {
+            mPreviousLine = mNextLine;
+            mNextLine = findNextLine();
+        }
+
+        public boolean isAtEndOfFile() {
+            return mNextLine == null;
+        }
+
+        /** Returns the previous line, for error messages. */
+        public String getPreviousLine() {
+            return mPreviousLine;
+        }
+
+        /** Returns the number of the <b>previous</b> line. */
+        public int getPreviousLineNumber() {
+            return mReader.getLineNumber() - 1;
+        }
+    }
+
+    public static class ParsingException extends RuntimeException {
+        private final int mLineNumber;
+        private final String mLine;
+
+        ParsingException(String message, CommentAwareReader reader) {
+            this(message, reader.getPreviousLine(), reader.getPreviousLineNumber());
+        }
+
+        ParsingException(String message, String line, int lineNumber) {
+            super(message);
+            mLineNumber = lineNumber;
+            mLine = line;
+        }
+
+        /** Returns a nicely formatted error message, including the line number and line. */
+        public String makeErrorMessage() {
+            return String.format("""
+                    Parsing error on line %d: %s
+                    --> %s
+                    """, mLineNumber, getMessage(), mLine);
+        }
+    }
+
+    private final CommentAwareReader mReader;
+    /**
+     * The timestamp of the last event returned, of the head of {@link #mQueuedEvents} if there is
+     * one, or -1 if no events have been returned yet.
+     */
+    private long mLastEventTimeMicros = -1;
+    private final Queue<Event> mQueuedEvents = new ArrayDeque<>(2);
+
+    public EvemuParser(Reader in) throws IOException {
+        mReader = new CommentAwareReader(new LineNumberReader(in));
+        mQueuedEvents.add(parseRegistrationEvent());
+
+        // The kernel takes a little time to set up an evdev device after the initial
+        // registration. Any events that we try to inject during this period would be silently
+        // dropped, so we delay for a short period after registration and before injecting any
+        // events.
+        final Event.Builder delayEb = new Event.Builder();
+        delayEb.setId(DEVICE_ID);
+        delayEb.setCommand(Event.Command.DELAY);
+        delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
+        mQueuedEvents.add(delayEb.build());
+    }
+
+    /**
+     * Returns the next event in the evemu recording.
+     */
+    public Event getNextEvent() throws IOException {
+        if (!mQueuedEvents.isEmpty()) {
+            return mQueuedEvents.remove();
+        }
+
+        if (mReader.isAtEndOfFile()) {
+            return null;
+        }
+
+        final String line = expectLine("E");
+        final String[] parts = expectParts(line, 4);
+        final String[] timeParts = parts[0].split("\\.");
+        if (timeParts.length != 2) {
+            throw new ParsingException(
+                    "Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader);
+        }
+        // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
+        final long timeMicros =
+                parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10);
+        final Event.Builder eb = new Event.Builder();
+        eb.setId(DEVICE_ID);
+        eb.setCommand(Event.Command.INJECT);
+        final int eventType = parseInt(parts[1], 16);
+        final int eventCode = parseInt(parts[2], 16);
+        final int value = parseInt(parts[3], 10);
+        eb.setInjections(new int[] {eventType, eventCode, value});
+
+        if (mLastEventTimeMicros == -1) {
+            // This is the first event being injected, so send it straight away.
+            mLastEventTimeMicros = timeMicros;
+            return eb.build();
+        } else {
+            final long delayMicros = timeMicros - mLastEventTimeMicros;
+            // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
+            // Device class) is 1ms, so ignore time differences smaller than that.
+            if (delayMicros < 1000) {
+                mLastEventTimeMicros = timeMicros;
+                return eb.build();
+            } else {
+                // Send a delay now, and queue the actual event for the next call.
+                mQueuedEvents.add(eb.build());
+                mLastEventTimeMicros = timeMicros;
+                final Event.Builder delayEb = new Event.Builder();
+                delayEb.setId(DEVICE_ID);
+                delayEb.setCommand(Event.Command.DELAY);
+                delayEb.setDurationMillis((int) (delayMicros / 1000));
+                return delayEb.build();
+            }
+        }
+    }
+
+    private Event parseRegistrationEvent() throws IOException {
+        // The registration details at the start of a recording are specified by a set of lines
+        // that have to be in this order: N, I, P, B, A, L, S. Recordings must have exactly one N
+        // (name) and I (IDs) line. The remaining lines are optional, and there may be multiple
+        // of those lines.
+
+        final Event.Builder eb = new Event.Builder();
+        eb.setId(DEVICE_ID);
+        eb.setCommand(Event.Command.REGISTER);
+        eb.setName(expectLine("N"));
+
+        final String idsLine = expectLine("I");
+        final String[] idStrings = expectParts(idsLine, 4);
+        eb.setBusId(parseInt(idStrings[0], 16));
+        eb.setVendorId(parseInt(idStrings[1], 16));
+        eb.setProductId(parseInt(idStrings[2], 16));
+        eb.setVersionId(parseInt(idStrings[3], 16));
+
+        final SparseArray<int[]> config = new SparseArray<>();
+        config.append(Event.UinputControlCode.UI_SET_PROPBIT.getValue(), parseProperties());
+
+        parseAxisBitmaps(config);
+
+        eb.setConfiguration(config);
+        if (config.contains(Event.UinputControlCode.UI_SET_FFBIT.getValue())) {
+            // If the device specifies any force feedback effects, the kernel will require the
+            // ff_effects_max value to be set.
+            eb.setFfEffectsMax(config.get(Event.UinputControlCode.UI_SET_FFBIT.getValue()).length);
+        }
+
+        eb.setAbsInfo(parseAbsInfos());
+
+        // L: and S: lines allow the initial states of the device's LEDs and switches to be
+        // recorded. However, the FreeDesktop implementation doesn't support actually setting these
+        // states at the start of playback (apparently due to concerns over race conditions), and we
+        // have no need for this feature either, so for now just skip over them.
+        skipUnsupportedLines("L");
+        skipUnsupportedLines("S");
+
+        return eb.build();
+    }
+
+    private int[] parseProperties() throws IOException {
+        final ArrayList<Integer> propBitmapParts = new ArrayList<>();
+        String line = acceptLine("P");
+        while (line != null) {
+            String[] parts = line.strip().split(" ");
+            propBitmapParts.ensureCapacity(propBitmapParts.size() + parts.length);
+            for (String part : parts) {
+                propBitmapParts.add(parseBitmapPart(part, line));
+            }
+            line = acceptLine("P");
+        }
+        return bitmapToEventCodes(propBitmapParts);
+    }
+
+    private void parseAxisBitmaps(SparseArray<int[]> config) throws IOException {
+        final Map<Integer, ArrayList<Integer>> axisBitmapParts = new HashMap<>();
+        String line = acceptLine("B");
+        while (line != null) {
+            final String[] parts = line.strip().split(" ");
+            if (parts.length < 2) {
+                throw new ParsingException(
+                        "Expected event type and at least one bitmap byte on 'B:' line; only found "
+                                + parts.length + " elements", mReader);
+            }
+            final int eventType = parseInt(parts[0], 16);
+            // EV_SYN cannot be configured through uinput, so skip it.
+            if (eventType != Event.EV_SYN) {
+                if (!axisBitmapParts.containsKey(eventType)) {
+                    axisBitmapParts.put(eventType, new ArrayList<>());
+                }
+                ArrayList<Integer> bitmapParts = axisBitmapParts.get(eventType);
+                bitmapParts.ensureCapacity(bitmapParts.size() + parts.length);
+                for (int i = 1; i < parts.length; i++) {
+                    axisBitmapParts.get(eventType).add(parseBitmapPart(parts[i], line));
+                }
+            }
+            line = acceptLine("B");
+        }
+        final List<Integer> eventTypesToSet = new ArrayList<>();
+        for (var entry : axisBitmapParts.entrySet()) {
+            if (entry.getValue().size() == 0) {
+                continue;
+            }
+            final Event.UinputControlCode controlCode =
+                    Event.UinputControlCode.forEventType(entry.getKey());
+            final int[] eventCodes = bitmapToEventCodes(entry.getValue());
+            if (controlCode != null && eventCodes.length > 0) {
+                config.append(controlCode.getValue(), eventCodes);
+                eventTypesToSet.add(entry.getKey());
+            }
+        }
+        config.append(
+                Event.UinputControlCode.UI_SET_EVBIT.getValue(), unboxIntList(eventTypesToSet));
+    }
+
+    private int parseBitmapPart(String part, String line) {
+        int b = parseInt(part, 16);
+        if (b < 0x0 || b > 0xff) {
+            throw new ParsingException("Bitmap part '" + part
+                    + "' invalid; parts must be hexadecimal values between 00 and ff.", mReader);
+        }
+        return b;
+    }
+
+    private SparseArray<InputAbsInfo> parseAbsInfos() throws IOException {
+        final SparseArray<InputAbsInfo> absInfos = new SparseArray<>();
+        String line = acceptLine("A");
+        while (line != null) {
+            final String[] parts = line.strip().split(" ");
+            if (parts.length < 5 || parts.length > 6) {
+                throw new ParsingException(
+                        "AbsInfo lines should have the format 'A: <index (hex)> <min> <max> <fuzz> "
+                                + "<flat> [<resolution>]'; expected 5 or 6 numbers but found "
+                                + parts.length, mReader);
+            }
+            final int axisCode = parseInt(parts[0], 16);
+            final InputAbsInfo info = new InputAbsInfo();
+            info.minimum = parseInt(parts[1], 10);
+            info.maximum = parseInt(parts[2], 10);
+            info.fuzz = parseInt(parts[3], 10);
+            info.flat = parseInt(parts[4], 10);
+            info.resolution = parts.length > 5 ? parseInt(parts[5], 10) : 0;
+            absInfos.append(axisCode, info);
+            line = acceptLine("A");
+        }
+        return absInfos;
+    }
+
+    private void skipUnsupportedLines(String type) throws IOException {
+        if (acceptLine(type) != null) {
+            while (acceptLine(type) != null) {
+                // Skip the line.
+            }
+        }
+    }
+
+    /**
+     * Returns the contents of the next line in the file if it has the given type, or raises an
+     * error if it does not.
+     *
+     * @param type the type of the line to expect, represented by the letter before the ':'.
+     * @return the part of the line after the ": ".
+     */
+    private String expectLine(String type) throws IOException {
+        final String line = acceptLine(type);
+        if (line == null) {
+            throw new ParsingException("Expected line of type '" + type + "'. (Lines should be in "
+                    + "the order N, I, P, B, A, L, S, E.)",
+                    mReader.peekLine(), mReader.getPreviousLineNumber() + 1);
+        } else {
+            return line;
+        }
+    }
+
+    /**
+     * Peeks at the next line in the file to see if it has the given type, and if so, returns its
+     * contents and advances the reader.
+     *
+     * @param type the type of the line to accept, represented by the letter before the ':'.
+     * @return the part of the line after the ": ", if the type matches; otherwise {@code null}.
+     */
+    private @Nullable String acceptLine(String type) throws IOException {
+        final String line = mReader.peekLine();
+        if (line == null) {
+            return null;
+        }
+        final String[] lineParts = line.split(": ", 2);
+        if (lineParts.length < 2) {
+            throw new ParsingException("Missing type separator ': '",
+                    line, mReader.getPreviousLineNumber() + 1);
+        }
+        if (lineParts[0].equals(type)) {
+            mReader.advance();
+            return lineParts[1];
+        } else {
+            return null;
+        }
+    }
+
+    private String[] expectParts(String line, int numParts) {
+        final String[] parts = line.strip().split(" ");
+        if (parts.length != numParts) {
+            throw new ParsingException(
+                    "Expected a line with " + numParts + " space-separated parts, but found one "
+                            + "with " + parts.length, mReader);
+        }
+        return parts;
+    }
+
+    private int parseInt(String s, int radix) {
+        try {
+            return Integer.parseInt(s, radix);
+        } catch (NumberFormatException ex) {
+            throw new ParsingException(
+                    "'" + s + "' is not a valid integer of base " + radix, mReader);
+        }
+    }
+
+    private long parseLong(String s, int radix) {
+        try {
+            return Long.parseLong(s, radix);
+        } catch (NumberFormatException ex) {
+            throw new ParsingException("'" + s + "' is not a valid long of base " + radix, mReader);
+        }
+    }
+
+    private static int[] bitmapToEventCodes(List<Integer> bytes) {
+        final List<Integer> codes = new ArrayList<>();
+        for (int iByte = 0; iByte < bytes.size(); iByte++) {
+            int b = bytes.get(iByte);
+            for (int iBit = 0; iBit < 8; iBit++) {
+                if ((b & 1) != 0) {
+                    codes.add(iByte * 8 + iBit);
+                }
+                b >>= 1;
+            }
+        }
+        return unboxIntList(codes);
+    }
+
+    private static int[] unboxIntList(List<Integer> list) {
+        final int[] array = new int[list.size()];
+        Arrays.setAll(array, list::get);
+        return array;
+    }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 01486c0..0f16a27 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -16,6 +16,7 @@
 
 package com.android.commands.uinput;
 
+import android.annotation.Nullable;
 import android.util.SparseArray;
 
 import java.util.Arrays;
@@ -30,7 +31,7 @@
 public class Event {
     private static final String TAG = "UinputEvent";
 
-    enum Command {
+    public enum Command {
         REGISTER,
         DELAY,
         INJECT,
@@ -39,6 +40,7 @@
 
     // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
     // kernel.
+    public static final int EV_SYN = 0x00;
     public static final int EV_KEY = 0x01;
     public static final int EV_REL = 0x02;
     public static final int EV_ABS = 0x03;
@@ -69,28 +71,33 @@
         public int getValue() {
             return mValue;
         }
-    }
 
-    // These constants come from "include/uapi/linux/input.h" in the kernel
-    enum Bus {
-        USB(0x03), BLUETOOTH(0x05);
-        private final int mValue;
-
-        Bus(int value) {
-            mValue = value;
-        }
-
-        int getValue() {
-            return mValue;
+        /**
+         * Returns the control code for the given evdev event type, or {@code null} if there is no
+         * control code for that type.
+         */
+        public static @Nullable UinputControlCode forEventType(int eventType) {
+            return switch (eventType) {
+                case EV_KEY -> UI_SET_KEYBIT;
+                case EV_REL -> UI_SET_RELBIT;
+                case EV_ABS -> UI_SET_ABSBIT;
+                case EV_MSC -> UI_SET_MSCBIT;
+                case EV_SW -> UI_SET_SWBIT;
+                case EV_LED -> UI_SET_LEDBIT;
+                case EV_SND -> UI_SET_SNDBIT;
+                case EV_FF -> UI_SET_FFBIT;
+                default -> null;
+            };
         }
     }
 
     private int mId;
     private Command mCommand;
     private String mName;
-    private int mVid;
-    private int mPid;
-    private Bus mBus;
+    private int mVendorId;
+    private int mProductId;
+    private int mVersionId;
+    private int mBusId;
     private int[] mInjections;
     private SparseArray<int[]> mConfiguration;
     private int mDurationMillis;
@@ -112,15 +119,19 @@
     }
 
     public int getVendorId() {
-        return mVid;
+        return mVendorId;
     }
 
     public int getProductId() {
-        return mPid;
+        return mProductId;
+    }
+
+    public int getVersionId() {
+        return mVersionId;
     }
 
     public int getBus() {
-        return mBus.getValue();
+        return mBusId;
     }
 
     public int[] getInjections() {
@@ -166,9 +177,9 @@
         return "Event{id=" + mId
             + ", command=" + mCommand
             + ", name=" + mName
-            + ", vid=" + mVid
-            + ", pid=" + mPid
-            + ", bus=" + mBus
+            + ", vid=" + mVendorId
+            + ", pid=" + mProductId
+            + ", busId=" + mBusId
             + ", events=" + Arrays.toString(mInjections)
             + ", configuration=" + mConfiguration
             + ", duration=" + mDurationMillis + "ms"
@@ -188,8 +199,8 @@
             mEvent.mId = id;
         }
 
-        public void setCommand(String command) {
-            mEvent.mCommand = Command.valueOf(command.toUpperCase());
+        public void setCommand(Command command) {
+            mEvent.mCommand = command;
         }
 
         public void setName(String name) {
@@ -210,16 +221,20 @@
             mEvent.mConfiguration = configuration;
         }
 
-        public void setVid(int vid) {
-            mEvent.mVid = vid;
+        public void setVendorId(int vendorId) {
+            mEvent.mVendorId = vendorId;
         }
 
-        public void setPid(int pid) {
-            mEvent.mPid = pid;
+        public void setProductId(int productId) {
+            mEvent.mProductId = productId;
         }
 
-        public void setBus(Bus bus) {
-            mEvent.mBus = bus;
+        public void setVersionId(int versionId) {
+            mEvent.mVersionId = versionId;
+        }
+
+        public void setBusId(int busId) {
+            mEvent.mBusId = busId;
         }
 
         public void setDurationMillis(int durationMillis) {
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/cmds/uinput/src/com/android/commands/uinput/EventParser.java
similarity index 60%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to cmds/uinput/src/com/android/commands/uinput/EventParser.java
index 8e55695..a4df03d 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/cmds/uinput/src/com/android/commands/uinput/EventParser.java
@@ -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,7 +14,16 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.commands.uinput;
 
-import org.robolectric.annotation.GraphicsMode;
+import java.io.IOException;
+
+/**
+ * Interface for a class that reads a stream of {@link Event}s.
+ */
+public interface EventParser {
+    /**
+     * Returns the next event in the file that the parser is reading from.
+     */
+    Event getNextEvent() throws IOException;
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
index 53d0be8..ed3ff33 100644
--- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -22,7 +22,7 @@
 import android.util.SparseArray;
 
 import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.Reader;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -34,12 +34,12 @@
 /**
  * A class that parses the JSON-like event format described in the README to build {@link Event}s.
  */
-public class JsonStyleParser {
+public class JsonStyleParser implements EventParser {
     private static final String TAG = "UinputJsonStyleParser";
 
     private JsonReader mReader;
 
-    public JsonStyleParser(InputStreamReader in) {
+    public JsonStyleParser(Reader in) {
         mReader = new JsonReader(in);
         mReader.setLenient(true);
     }
@@ -57,11 +57,12 @@
                     String name = mReader.nextName();
                     switch (name) {
                         case "id" -> eb.setId(readInt());
-                        case "command" -> eb.setCommand(mReader.nextString());
+                        case "command" -> eb.setCommand(
+                                Event.Command.valueOf(mReader.nextString().toUpperCase()));
                         case "name" -> eb.setName(mReader.nextString());
-                        case "vid" -> eb.setVid(readInt());
-                        case "pid" -> eb.setPid(readInt());
-                        case "bus" -> eb.setBus(readBus());
+                        case "vid" -> eb.setVendorId(readInt());
+                        case "pid" -> eb.setProductId(readInt());
+                        case "bus" -> eb.setBusId(readBus());
                         case "events" -> {
                             int[] injections = readInjectedEvents().stream()
                                     .mapToInt(Integer::intValue).toArray();
@@ -138,9 +139,35 @@
         });
     }
 
-    private Event.Bus readBus() throws IOException {
+    private int readBus() throws IOException {
         String val = mReader.nextString();
-        return Event.Bus.valueOf(val.toUpperCase());
+        // See include/uapi/linux/input.h in the kernel for the source of these constants.
+        return switch (val.toUpperCase()) {
+            case "PCI" -> 0x01;
+            case "ISAPNP" -> 0x02;
+            case "USB" -> 0x03;
+            case "HIL" -> 0x04;
+            case "BLUETOOTH" -> 0x05;
+            case "VIRTUAL" -> 0x06;
+            case "ISA" -> 0x10;
+            case "I8042" -> 0x11;
+            case "XTKBD" -> 0x12;
+            case "RS232" -> 0x13;
+            case "GAMEPORT" -> 0x14;
+            case "PARPORT" -> 0x15;
+            case "AMIGA" -> 0x16;
+            case "ADB" -> 0x17;
+            case "I2C" -> 0x18;
+            case "HOST" -> 0x19;
+            case "GSC" -> 0x1A;
+            case "ATARI" -> 0x1B;
+            case "SPI" -> 0x1C;
+            case "RMI" -> 0x1D;
+            case "CEC" -> 0x1E;
+            case "INTEL_ISHTP" -> 0x1F;
+            case "AMD_SFH" -> 0x20;
+            default -> throw new IllegalArgumentException("Invalid bus ID " + val);
+        };
     }
 
     private SparseArray<int[]> readConfiguration()
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index fe76abb..04df279 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -19,12 +19,12 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
 import java.util.Objects;
 
 /**
@@ -35,7 +35,7 @@
 public class Uinput {
     private static final String TAG = "UINPUT";
 
-    private final JsonStyleParser mParser;
+    private final EventParser mParser;
     private final SparseArray<Device> mDevices;
 
     private static void usage() {
@@ -60,6 +60,10 @@
                 stream = new FileInputStream(f);
             }
             (new Uinput(stream)).run();
+        } catch (EvemuParser.ParsingException e) {
+            System.err.println(e.makeErrorMessage());
+            error(e.makeErrorMessage(), e);
+            System.exit(1);
         } catch (Exception e) {
             error("Uinput injection failed.", e);
             System.exit(1);
@@ -74,12 +78,32 @@
     private Uinput(InputStream in) {
         mDevices = new SparseArray<Device>();
         try {
-            mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8"));
-        } catch (UnsupportedEncodingException e) {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+            mParser = isEvemuFile(reader) ? new EvemuParser(reader) : new JsonStyleParser(reader);
+        } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 
+    private boolean isEvemuFile(BufferedReader in) throws IOException {
+        // After zero or more empty lines (not even containing horizontal whitespace), evemu
+        // recordings must either start with '#' (indicating the EVEMU version header or a comment)
+        // or 'N' (for the name line). If we encounter anything else, assume it's a JSON-style input
+        // file.
+
+        String lineSep = System.lineSeparator();
+        char[] buf = new char[1];
+
+        in.mark(1 /* readAheadLimit */);
+        int charsRead = in.read(buf);
+        while (charsRead > 0 && lineSep.contains(String.valueOf(buf[0]))) {
+            in.mark(1 /* readAheadLimit */);
+            charsRead = in.read(buf);
+        }
+        in.reset();
+        return buf[0] == '#' || buf[0] == 'N';
+    }
+
     private void run() {
         try {
             Event e = null;
@@ -122,8 +146,9 @@
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
         int id = e.getId();
-        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
-                e.getConfiguration(), e.getFfEffectsMax(), e.getAbsInfo(), e.getPort());
+        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
+                e.getVersionId(), e.getBus(), e.getConfiguration(), e.getFfEffectsMax(),
+                e.getAbsInfo(), e.getPort());
         mDevices.append(id, d);
     }
 
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
new file mode 100644
index 0000000..e728bd2
--- /dev/null
+++ b/cmds/uinput/tests/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["frameworks_base_cmds_uinput_license"],
+}
+
+android_test {
+    name: "UinputTests",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "frameworks-base-testutils",
+        "platform-test-annotations",
+        "truth",
+        "uinput",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+}
diff --git a/cmds/uinput/tests/AndroidManifest.xml b/cmds/uinput/tests/AndroidManifest.xml
new file mode 100644
index 0000000..c364c1c
--- /dev/null
+++ b/cmds/uinput/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.commands.uinput.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Uinput Tests"
+        android:targetPackage="com.android.commands.uinput.tests" />
+</manifest>
diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
new file mode 100644
index 0000000..06b0aac2
--- /dev/null
+++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.uinput.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Postsubmit;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.commands.uinput.EvemuParser;
+import com.android.commands.uinput.Event;
+import com.android.commands.uinput.Event.UinputControlCode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Postsubmit
+public class EvemuParserTest {
+
+    private Event getRegistrationEvent(String fileContents) throws IOException {
+        StringReader reader = new StringReader(fileContents);
+        EvemuParser parser = new EvemuParser(reader);
+        Event event = parser.getNextEvent();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.REGISTER);
+        return event;
+    }
+
+    @Test
+    public void testNameParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                """);
+        assertThat(event.getName()).isEqualTo("ACME Pointing Widget #4");
+    }
+
+    @Test
+    public void testIdParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                """);
+        assertThat(event.getBus()).isEqualTo(0x0001);
+        assertThat(event.getVendorId()).isEqualTo(0x1234);
+        assertThat(event.getProductId()).isEqualTo(0x5678);
+        assertThat(event.getVersionId()).isEqualTo(0x9abc);
+    }
+
+    @Test
+    public void testPropertyBitmapParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                P: 05 00 00 00 00 00 00 00
+                P: 01
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
+                .asList().containsExactly(0, 2, 64);
+    }
+
+    @Test
+    public void testEventBitmapParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                B: 00 0b 00 00 00 00 00 00 00  # SYN
+                B: 01 00 00 03 00 00 00 00 00  # KEY
+                B: 01 00 01 00 00 00 00 00 00
+                B: 02 03 00 00 00 00 00 00 00  # REL
+                B: 03 00 00                    # ABS
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_KEY, Event.EV_REL);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue()))
+                .asList().containsExactly(16, 17, 72);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_RELBIT.getValue()))
+                .asList().containsExactly(0, 1);
+        assertThat(event.getConfiguration().contains(UinputControlCode.UI_SET_ABSBIT.getValue()))
+                .isFalse();
+    }
+
+    @Test
+    public void testEventBitmapParsing_WithForceFeedback() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                B: 15 05  # FF
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_FF);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_FFBIT.getValue()))
+                .asList().containsExactly(0, 2);
+        assertThat(event.getFfEffectsMax()).isEqualTo(2);
+    }
+
+    private void assertAbsInfo(InputAbsInfo info, int minimum, int maximum, int fuzz, int flat,
+                               int resolution) {
+        assertThat(info).isNotNull();
+        assertWithMessage("Incorrect minimum").that(info.minimum).isEqualTo(minimum);
+        assertWithMessage("Incorrect maximum").that(info.maximum).isEqualTo(maximum);
+        assertWithMessage("Incorrect fuzz").that(info.fuzz).isEqualTo(fuzz);
+        assertWithMessage("Incorrect flat").that(info.flat).isEqualTo(flat);
+        assertWithMessage("Incorrect resolution").that(info.resolution).isEqualTo(resolution);
+    }
+
+    @Test
+    public void testAbsInfoParsing_WithResolution() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Weird Gamepad
+                I: 0001 1234 5678 9abc
+                A: 03 -128 128 4 4 0    # ABS_MT_RX
+                A: 2f 0 9 0 0 0         # ABS_MT_SLOT
+                A: 34 -4096 4096 0 0 0  # ABS_MT_ORIENTATION
+                A: 35 0 1599 0 0 11     # ABS_MT_POSITION_X
+                """);
+        SparseArray<InputAbsInfo> absInfos = event.getAbsInfo();
+        assertThat(absInfos.size()).isEqualTo(4);
+        assertAbsInfo(absInfos.get(0x03), -128, 128, 4, 4, 0);
+        assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x34), -4096, 4096, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 11);
+    }
+
+    @Test
+    public void testAbsInfoParsing_WithoutResolution() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Terrible Touchscreen
+                I: 0001 1234 5678 9abc
+                A: 2f 0 9 0 0         # ABS_MT_SLOT
+                A: 35 0 1599 0 0      # ABS_MT_POSITION_X
+                A: 36 0 2559 0 0      # ABS_MT_POSITION_X
+                """);
+        SparseArray<InputAbsInfo> absInfos = event.getAbsInfo();
+        assertThat(absInfos.size()).isEqualTo(3);
+        assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x36), 0, 2559, 0, 0, 0);
+    }
+
+    @Test
+    public void testLedAndSwitchStatesIgnored() throws IOException {
+        // We don't support L: and S: lines yet, so all we need to check here is that they don't
+        // prevent the other events from being parsed.
+        StringReader reader = new StringReader("""
+                N: ACME Widget
+                I: 0001 1234 5678 9abc
+                L: 00 0
+                L: 09 1
+                S: 0a 1
+                E: 0.000001 0 0 0  # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.INJECT);
+    }
+
+    private void assertInjectEvent(Event event, int eventType, int eventCode, int value) {
+        assertThat(event).isNotNull();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT);
+        assertThat(event.getInjections()).asList()
+                .containsExactly(eventType, eventCode, value).inOrder();
+    }
+
+    private void assertDelayEvent(Event event, int durationMillis) {
+        assertThat(event).isNotNull();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY);
+        assertThat(event.getDurationMillis()).isEqualTo(durationMillis);
+    }
+
+    @Test
+    public void testEventParsing_OneFrame() throws IOException {
+        StringReader reader = new StringReader("""
+                N: ACME Widget
+                I: 0001 1234 5678 9abc
+                E: 0.000001 0002 0000 0001   # REL_X +1
+                E: 0.000001 0002 0001 -0002  # REL_Y -2
+                E: 0.000001 0000 0000 0000   # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+        assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+    }
+
+    @Test
+    public void testEventParsing_MultipleFrames() throws IOException {
+        StringReader reader = new StringReader("""
+                N: ACME YesBird Typing Aid
+                I: 0001 1234 5678 9abc
+                E: 0.000001 0001 0015 0001   # KEY_Y press
+                E: 0.000001 0000 0000 0000   # SYN_REPORT
+                E: 0.010001 0001 0015 0000   # KEY_Y release
+                E: 0.010001 0000 0000 0000   # SYN_REPORT
+                E: 1.010001 0001 0015 0001   # KEY_Y press
+                E: 1.010001 0000 0000 0000   # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 10);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 1000);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+    }
+
+    @Test
+    public void testErrorLineNumberReporting() throws IOException {
+        StringReader reader = new StringReader("""
+                # EVEMU 1.3
+                N: ACME Widget
+                # Comment to make sure they're taken into account when numbering lines
+                I: 0001 1234 5678 9abc
+                00 00 00 00 00 00 00 00  # Missing a type
+                E: 0.000001 0001 0015 0001   # KEY_Y press
+                E: 0.000001 0000 0000 0000   # SYN_REPORT
+                """);
+        try {
+            new EvemuParser(reader);
+            fail("Parser should have thrown an error about the line with the missing type.");
+        } catch (EvemuParser.ParsingException ex) {
+            assertThat(ex.makeErrorMessage()).startsWith("Parsing error on line 5:");
+        }
+    }
+
+    @Test
+    public void testFreeDesktopEvemuRecording() throws IOException {
+        // This is a real recording from FreeDesktop's evemu-record tool, as a basic compatibility
+        // check with the FreeDesktop tools.
+        // (CheckStyle objects to the long line here. It can be split up with escaped newlines once
+        // the fix for b/306423115 reaches Android.)
+        StringReader reader = new StringReader("""
+                # EVEMU 1.3
+                # Kernel: 6.5.6-1rodete4-amd64
+                # DMI: dmi:bvnLENOVO:bvrXXXXXXXX(X.XX):bdXX/XX/XXXX:brX.XX:efrX.XX:svnLENOVO:pnXXXXXXXXXX:pvrThinkPadX1Carbon:rvnLENOVO:rnXXXXXXXXX:rvrXXXXX:cvnLENOVO:ctXX:cvrNone:skuLENOVO_MT_20KG_BU_Think_FM_ThinkPadX1Carbon:
+                # Input device name: "Synaptics TM3289-021"
+                # Input device ID: bus 0x1d vendor 0x6cb product 0000 version 0000
+                # Size in mm: 96x52
+                # Supported events:
+                #   Event type 0 (EV_SYN)
+                #     Event code 0 (SYN_REPORT)
+                #     Event code 1 (SYN_CONFIG)
+                #     Event code 2 (SYN_MT_REPORT)
+                #     Event code 3 (SYN_DROPPED)
+                #     Event code 4 ((null))
+                #     Event code 5 ((null))
+                #     Event code 6 ((null))
+                #     Event code 7 ((null))
+                #     Event code 8 ((null))
+                #     Event code 9 ((null))
+                #     Event code 10 ((null))
+                #     Event code 11 ((null))
+                #     Event code 12 ((null))
+                #     Event code 13 ((null))
+                #     Event code 14 ((null))
+                #     Event code 15 (SYN_MAX)
+                #   Event type 1 (EV_KEY)
+                #     Event code 272 (BTN_LEFT)
+                #     Event code 325 (BTN_TOOL_FINGER)
+                #     Event code 328 (BTN_TOOL_QUINTTAP)
+                #     Event code 330 (BTN_TOUCH)
+                #     Event code 333 (BTN_TOOL_DOUBLETAP)
+                #     Event code 334 (BTN_TOOL_TRIPLETAP)
+                #     Event code 335 (BTN_TOOL_QUADTAP)
+                #   Event type 3 (EV_ABS)
+                #     Event code 0 (ABS_X)
+                #       Value        0
+                #       Min          0
+                #       Max       1936
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 1 (ABS_Y)
+                #       Value        0
+                #       Min          0
+                #       Max       1057
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 24 (ABS_PRESSURE)
+                #       Value        0
+                #       Min          0
+                #       Max        255
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 47 (ABS_MT_SLOT)
+                #       Value        0
+                #       Min          0
+                #       Max          4
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 48 (ABS_MT_TOUCH_MAJOR)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 49 (ABS_MT_TOUCH_MINOR)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 52 (ABS_MT_ORIENTATION)
+                #       Value        0
+                #       Min          0
+                #       Max          1
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 53 (ABS_MT_POSITION_X)
+                #       Value        0
+                #       Min          0
+                #       Max       1936
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 54 (ABS_MT_POSITION_Y)
+                #       Value        0
+                #       Min          0
+                #       Max       1057
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 55 (ABS_MT_TOOL_TYPE)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 57 (ABS_MT_TRACKING_ID)
+                #       Value        0
+                #       Min          0
+                #       Max      65535
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 58 (ABS_MT_PRESSURE)
+                #       Value        0
+                #       Min          0
+                #       Max        255
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                # Properties:
+                #   Property  type 0 (INPUT_PROP_POINTER)
+                #   Property  type 2 (INPUT_PROP_BUTTONPAD)
+                N: Synaptics TM3289-021
+                I: 001d 06cb 0000 0000
+                P: 05 00 00 00 00 00 00 00
+                B: 00 0b 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 01 00 00 00 00 00
+                B: 01 20 e5 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 02 00 00 00 00 00 00 00 00
+                B: 03 03 00 00 01 00 80 f3 06
+                B: 04 00 00 00 00 00 00 00 00
+                B: 05 00 00 00 00 00 00 00 00
+                B: 11 00 00 00 00 00 00 00 00
+                B: 12 00 00 00 00 00 00 00 00
+                B: 14 00 00 00 00 00 00 00 00
+                B: 15 00 00 00 00 00 00 00 00
+                B: 15 00 00 00 00 00 00 00 00
+                A: 00 0 1936 0 0 20
+                A: 01 0 1057 0 0 20
+                A: 18 0 255 0 0 0
+                A: 2f 0 4 0 0 0
+                A: 30 0 15 0 0 0
+                A: 31 0 15 0 0 0
+                A: 34 0 1 0 0 0
+                A: 35 0 1936 0 0 20
+                A: 36 0 1057 0 0 20
+                A: 37 0 15 0 0 0
+                A: 39 0 65535 0 0 0
+                A: 3a 0 255 0 0 0
+                ################################
+                #      Waiting for events      #
+                ################################
+                E: 0.000001 0003 0039 0000\t# EV_ABS / ABS_MT_TRACKING_ID   0
+                E: 0.000001 0003 0035 0891\t# EV_ABS / ABS_MT_POSITION_X    891
+                E: 0.000001 0003 0036 0333\t# EV_ABS / ABS_MT_POSITION_Y    333
+                E: 0.000001 0003 003a 0056\t# EV_ABS / ABS_MT_PRESSURE      56
+                E: 0.000001 0003 0030 0001\t# EV_ABS / ABS_MT_TOUCH_MAJOR   1
+                E: 0.000001 0003 0031 0001\t# EV_ABS / ABS_MT_TOUCH_MINOR   1
+                E: 0.000001 0001 014a 0001\t# EV_KEY / BTN_TOUCH            1
+                E: 0.000001 0001 0145 0001\t# EV_KEY / BTN_TOOL_FINGER      1
+                E: 0.000001 0003 0000 0891\t# EV_ABS / ABS_X                891
+                E: 0.000001 0003 0001 0333\t# EV_ABS / ABS_Y                333
+                E: 0.000001 0003 0018 0056\t# EV_ABS / ABS_PRESSURE         56
+                E: 0.000001 0000 0000 0000\t# ------------ SYN_REPORT (0) ---------- +0ms
+                E: 0.006081 0003 0035 0888\t# EV_ABS / ABS_MT_POSITION_X    888
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        Event regEvent = parser.getNextEvent();
+        assertThat(regEvent.getName()).isEqualTo("Synaptics TM3289-021");
+
+        assertThat(regEvent.getBus()).isEqualTo(0x001d);
+        assertThat(regEvent.getVendorId()).isEqualTo(0x6cb);
+        assertThat(regEvent.getProductId()).isEqualTo(0x0000);
+        // TODO(b/302297266): check version ID once it's supported
+
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
+                .asList().containsExactly(0, 2);
+
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_KEY, Event.EV_ABS);
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue()))
+                .asList().containsExactly(272, 325, 328, 330, 333, 334, 335);
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_ABSBIT.getValue()))
+                .asList().containsExactly(0, 1, 24, 47, 48, 49, 52, 53, 54, 55, 57, 58);
+
+        SparseArray<InputAbsInfo> absInfos = regEvent.getAbsInfo();
+        assertAbsInfo(absInfos.get(0), 0, 1936, 0, 0, 20);
+        assertAbsInfo(absInfos.get(1), 0, 1057, 0, 0, 20);
+        assertAbsInfo(absInfos.get(24), 0, 255, 0, 0, 0);
+        assertAbsInfo(absInfos.get(47), 0, 4, 0, 0, 0);
+        assertAbsInfo(absInfos.get(48), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(49), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(52), 0, 1, 0, 0, 0);
+        assertAbsInfo(absInfos.get(53), 0, 1936, 0, 0, 20);
+        assertAbsInfo(absInfos.get(54), 0, 1057, 0, 0, 20);
+        assertAbsInfo(absInfos.get(55), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(57), 0, 65535, 0, 0, 0);
+        assertAbsInfo(absInfos.get(58), 0, 255, 0, 0, 0);
+
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x30, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x31, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x14a, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x145, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0, 891);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x1, 333);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 6);
+
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888);
+    }
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 89ed896..3091354 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6904,6 +6904,7 @@
 
   public class NotificationManager {
     method public String addAutomaticZenRule(android.app.AutomaticZenRule);
+    method @FlaggedApi("android.app.modes_api") public boolean areAutomaticZenRulesUserManaged();
     method @Deprecated public boolean areBubblesAllowed();
     method public boolean areBubblesEnabled();
     method public boolean areNotificationsEnabled();
@@ -8858,6 +8859,7 @@
     method public int getBackoffPolicy();
     method @Nullable public android.content.ClipData getClipData();
     method public int getClipGrantFlags();
+    method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public java.util.Set<java.lang.String> getDebugTags();
     method public long getEstimatedNetworkDownloadBytes();
     method public long getEstimatedNetworkUploadBytes();
     method @NonNull public android.os.PersistableBundle getExtras();
@@ -8874,6 +8876,7 @@
     method public int getPriority();
     method @Nullable public android.net.NetworkRequest getRequiredNetwork();
     method @NonNull public android.content.ComponentName getService();
+    method @FlaggedApi("android.app.job.job_debug_info_apis") @Nullable public String getTraceTag();
     method @NonNull public android.os.Bundle getTransientExtras();
     method public long getTriggerContentMaxDelay();
     method public long getTriggerContentUpdateDelay();
@@ -8910,8 +8913,10 @@
 
   public static final class JobInfo.Builder {
     ctor public JobInfo.Builder(int, @NonNull android.content.ComponentName);
+    method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder addDebugTag(@NonNull String);
     method public android.app.job.JobInfo.Builder addTriggerContentUri(@NonNull android.app.job.JobInfo.TriggerContentUri);
     method public android.app.job.JobInfo build();
+    method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder removeDebugTag(@NonNull String);
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setClipData(@Nullable android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long);
@@ -8932,6 +8937,7 @@
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
     method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
     method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
+    method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder setTraceTag(@Nullable String);
     method public android.app.job.JobInfo.Builder setTransientExtras(@NonNull android.os.Bundle);
     method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
     method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -9336,6 +9342,7 @@
     method public String getClassName();
     method public android.content.res.Configuration getConfiguration();
     method public int getEventType();
+    method @FlaggedApi("android.app.usage.user_interaction_type_api") @NonNull public android.os.PersistableBundle getExtras();
     method public String getPackageName();
     method public String getShortcutId();
     method public long getTimeStamp();
@@ -9401,6 +9408,8 @@
     method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
     method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
+    field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
+    field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_CATEGORY = "android.app.usage.extra.EVENT_CATEGORY";
     field public static final int INTERVAL_BEST = 4; // 0x4
     field public static final int INTERVAL_DAILY = 0; // 0x0
     field public static final int INTERVAL_MONTHLY = 2; // 0x2
@@ -10405,7 +10414,7 @@
     method public abstract java.io.File getDatabasePath(String);
     method public int getDeviceId();
     method public abstract java.io.File getDir(String, int);
-    method @Nullable public android.view.Display getDisplay();
+    method @NonNull public android.view.Display getDisplay();
     method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int);
     method @Nullable public abstract java.io.File getExternalCacheDir();
     method public abstract java.io.File[] getExternalCacheDirs();
@@ -12775,6 +12784,7 @@
     method public boolean isPackageSuspended();
     method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
     method public abstract boolean isSafeMode();
+    method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -12848,6 +12858,7 @@
     field public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
     field public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
+    field @FlaggedApi("android.view.inputmethod.concurrent_input_methods") public static final String FEATURE_CONCURRENT_INPUT_METHODS = "android.software.concurrent_input_methods";
     field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final String FEATURE_CONTROLS = "android.software.controls";
@@ -16991,6 +17002,7 @@
     ctor public YuvImage(@NonNull byte[], int, int, int, @Nullable int[], @NonNull android.graphics.ColorSpace);
     method public boolean compressToJpeg(android.graphics.Rect, int, java.io.OutputStream);
     method public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream);
+    method @FlaggedApi("com.android.graphics.flags.yuv_image_compress_to_ultra_hdr") public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream, @NonNull byte[]);
     method @NonNull public android.graphics.ColorSpace getColorSpace();
     method public int getHeight();
     method public int[] getStrides();
@@ -33150,7 +33162,6 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
-    method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration);
     method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean);
     method public void setThreads(@NonNull int[]);
     method public void updateTargetWorkDuration(long);
@@ -33493,7 +33504,6 @@
     method public static boolean setCurrentTimeMillis(long);
     method public static void sleep(long);
     method public static long uptimeMillis();
-    method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos();
   }
 
   public class TestLooperManager {
@@ -33759,22 +33769,6 @@
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes);
   }
 
-  @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
-    ctor public WorkDuration();
-    ctor public WorkDuration(long, long, long, long);
-    method public int describeContents();
-    method public long getActualCpuDurationNanos();
-    method public long getActualGpuDurationNanos();
-    method public long getActualTotalDurationNanos();
-    method public long getWorkPeriodStartTimestampNanos();
-    method public void setActualCpuDurationNanos(long);
-    method public void setActualGpuDurationNanos(long);
-    method public void setActualTotalDurationNanos(long);
-    method public void setWorkPeriodStartTimestampNanos(long);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR;
-  }
-
   public class WorkSource implements android.os.Parcelable {
     ctor public WorkSource();
     ctor public WorkSource(android.os.WorkSource);
@@ -36770,6 +36764,7 @@
     field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
     field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
     field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
+    field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
     field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
     field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
     field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
@@ -36857,6 +36852,7 @@
     field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
     field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
     field public static final String EXTRA_AUTHORITIES = "authorities";
+    field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
     field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
     field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
     field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
@@ -39258,7 +39254,7 @@
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@Nullable java.lang.String...);
+    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -40544,19 +40540,26 @@
 
   public final class Condition implements android.os.Parcelable {
     ctor public Condition(android.net.Uri, String, int);
+    ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, int, int);
     ctor public Condition(android.net.Uri, String, String, String, int, int, int);
+    ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, @Nullable String, @Nullable String, int, int, int, int);
     ctor public Condition(android.os.Parcel);
     method public android.service.notification.Condition copy();
     method public int describeContents();
     method public static boolean isValidId(android.net.Uri, String);
     method public static android.net.Uri.Builder newId(android.content.Context);
     method public static String relevanceToString(int);
+    method @FlaggedApi("android.app.modes_api") @NonNull public static String sourceToString(int);
     method public static String stateToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Condition> CREATOR;
     field public static final int FLAG_RELEVANT_ALWAYS = 2; // 0x2
     field public static final int FLAG_RELEVANT_NOW = 1; // 0x1
     field public static final String SCHEME = "condition";
+    field @FlaggedApi("android.app.modes_api") public static final int SOURCE_CONTEXT = 3; // 0x3
+    field @FlaggedApi("android.app.modes_api") public static final int SOURCE_SCHEDULE = 2; // 0x2
+    field @FlaggedApi("android.app.modes_api") public static final int SOURCE_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.app.modes_api") public static final int SOURCE_USER_ACTION = 1; // 0x1
     field public static final int STATE_ERROR = 3; // 0x3
     field public static final int STATE_FALSE = 0; // 0x0
     field public static final int STATE_TRUE = 1; // 0x1
@@ -40566,6 +40569,7 @@
     field public final android.net.Uri id;
     field public final String line1;
     field public final String line2;
+    field @FlaggedApi("android.app.modes_api") public final int source;
     field public final int state;
     field public final String summary;
   }
@@ -40756,6 +40760,7 @@
 
   public final class ZenPolicy implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("android.app.modes_api") public int getAllowedChannels();
     method public int getPriorityCallSenders();
     method public int getPriorityCategoryAlarms();
     method public int getPriorityCategoryCalls();
@@ -40776,6 +40781,9 @@
     method public int getVisualEffectPeek();
     method public int getVisualEffectStatusBar();
     method public void writeToParcel(android.os.Parcel, int);
+    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_NONE = 2; // 0x2
+    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_PRIORITY = 1; // 0x1
+    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_UNSET = 0; // 0x0
     field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1
     field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2
     field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3
@@ -40796,6 +40804,7 @@
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
     method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
+    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowChannels(int);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
@@ -43736,7 +43745,9 @@
     field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array";
     field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array";
     field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_multiple_sa_proposals") public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = "iwlan.supports_child_session_multiple_sa_proposals_bool";
     field public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.supports_eap_aka_fast_reauth_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_multiple_sa_proposals") public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = "iwlan.supports_ike_session_multiple_sa_proposals_bool";
   }
 
   public abstract class CellIdentity implements android.os.Parcelable {
@@ -45162,6 +45173,7 @@
     method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
     method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -54186,7 +54198,7 @@
     method public boolean isEnabled();
     method public boolean isFocusable();
     method public boolean isFocused();
-    method public boolean isGranularScrollingSupported();
+    method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
     method public boolean isHeading();
     method public boolean isImportantForAccessibility();
     method public boolean isLongClickable();
@@ -54236,7 +54248,7 @@
     method public void setError(CharSequence);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
-    method public void setGranularScrollingSupported(boolean);
+    method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
     method public void setHeading(boolean);
     method public void setHintText(CharSequence);
     method public void setImportantForAccessibility(boolean);
@@ -54291,7 +54303,7 @@
     field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
     field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
     field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
-    field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+    field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
     field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
     field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
     field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
@@ -54394,10 +54406,9 @@
   public static final class AccessibilityNodeInfo.CollectionInfo {
     ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean);
     ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int);
-    ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int);
     method public int getColumnCount();
-    method public int getImportantForAccessibilityItemCount();
-    method public int getItemCount();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount();
     method public int getRowCount();
     method public int getSelectionMode();
     method public boolean isHierarchical();
@@ -54406,18 +54417,18 @@
     field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
     field public static final int SELECTION_MODE_NONE = 0; // 0x0
     field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
-    field public static final int UNDEFINED = -1; // 0xffffffff
+    field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff
   }
 
-  public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
-    ctor public AccessibilityNodeInfo.CollectionInfo.Builder();
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
-    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
+  @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
+    ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
+    method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
   }
 
   public static final class AccessibilityNodeInfo.CollectionItemInfo {
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 449249e..f331e7f 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -389,6 +389,12 @@
     Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
 
 
+InvalidNullabilityOverride: android.app.Notification.TvExtender#extend(android.app.Notification.Builder) parameter #0:
+    Invalid nullability on parameter `builder` in method `extend`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(android.content.Intent) parameter #0:
+    Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+
+
 RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
     Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index df466ab..e7803fb 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -639,3 +639,12 @@
 
 }
 
+package android.view.accessibility {
+
+  public final class AccessibilityManager {
+    method @FlaggedApi("android.view.accessibility.flash_notification_system_api") public boolean startFlashNotificationSequence(@NonNull android.content.Context, int);
+    method @FlaggedApi("android.view.accessibility.flash_notification_system_api") public boolean stopFlashNotificationSequence(@NonNull android.content.Context);
+  }
+
+}
+
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 989bb77..285dcc6a 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -112,6 +112,9 @@
     method public abstract boolean setInstantAppCookie(@Nullable byte[]);
   }
 
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x100, 0x200, 0x2000, 0x1000, 0x800, 0x4000, 0x8000, 0x8, 0x10000, 0x20000}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  }
+
   public final class SharedLibraryInfo implements android.os.Parcelable {
     method public boolean isBuiltin();
     method public boolean isDynamic();
@@ -318,6 +321,9 @@
     method public CharSequence getBadgedLabelForUser(CharSequence, android.os.UserHandle);
   }
 
+  @IntDef(flag=true, prefix={"RESTRICTION_"}, value={0x0, 0x1, 0x2, 0x4}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource {
+  }
+
 }
 
 package android.os.storage {
@@ -493,6 +499,13 @@
 
 }
 
+package android.telephony.euicc {
+
+  @IntDef(prefix={"EUICC_OTA_"}, value={0x1, 0x2, 0x3, 0x4, 0x5}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
+  }
+
+}
+
 package android.text.format {
 
   public class DateFormat {
@@ -554,6 +567,9 @@
     field public static final int TYPE_STATUS_BAR_PANEL = 2014; // 0x7de
   }
 
+  @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={0x80000, 0x10}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
+  }
+
 }
 
 package android.view.accessibility {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index afea9b5..fc23f9b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -258,6 +258,7 @@
     field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
+    field @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis") public static final String PREPARE_FACTORY_RESET = "android.permission.PREPARE_FACTORY_RESET";
     field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
@@ -307,7 +308,7 @@
     field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
     field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
     field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
-    field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
+    field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
     field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
     field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
     field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
@@ -3861,11 +3862,16 @@
     field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
   }
 
+  public class PackageInfo implements android.os.Parcelable {
+    method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis();
+  }
+
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
-    method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+    method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3877,18 +3883,27 @@
     field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
     field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
     field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
     field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
     field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
     field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
   }
 
   public static class PackageInstaller.InstallInfo {
     method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
-    method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+    method @FlaggedApi("android.content.pm.read_install_info") public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
     method public int getInstallLocation();
     method @NonNull public String getPackageName();
   }
@@ -3933,6 +3948,7 @@
     method public void setRequestDowngrade(boolean);
     method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
+    method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int);
   }
 
   public class PackageItemInfo {
@@ -3966,7 +3982,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public String getPermissionControllerPackageName();
-    method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -3991,13 +4007,13 @@
     method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
     method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
-    method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.QUARANTINE_APPS}, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
     method @NonNull public boolean shouldShowNewAppInstalledNotification();
     method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
@@ -4114,9 +4130,7 @@
 
   public static interface PackageManager.OnPermissionsChangedListener {
     method public void onPermissionsChanged(int);
-  }
-
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String);
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
@@ -4198,10 +4212,18 @@
 
   public final class UserProperties implements android.os.Parcelable {
     method public int describeContents();
+    method public int getShowInQuietMode();
+    method public int getShowInSharingSurfaces();
     method public boolean isCredentialShareableWithParent();
     method public boolean isMediaSharedWithParent();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+    field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
+    field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
+    field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
+    field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2
+    field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0
   }
 
 }
@@ -4620,7 +4642,7 @@
   }
 
   public static interface HdmiClient.OnDeviceSelectedListener {
-    method public void onDeviceSelected(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int, int);
+    method public void onDeviceSelected(int, int);
   }
 
   public final class HdmiControlManager {
@@ -4818,9 +4840,6 @@
     method public void onChange(@NonNull String);
   }
 
-  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult {
-  }
-
   public static interface HdmiControlManager.HotplugEventListener {
     method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
   }
@@ -4967,7 +4986,7 @@
   }
 
   public static interface HdmiSwitchClient.OnSelectListener {
-    method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int);
+    method public void onSelect(int);
   }
 
   public class HdmiTimerRecordSources {
@@ -5684,14 +5703,14 @@
   }
 
   public final class ProgramSelector implements android.os.Parcelable {
-    ctor public ProgramSelector(@android.hardware.radio.ProgramSelector.ProgramType int, @NonNull android.hardware.radio.ProgramSelector.Identifier, @Nullable android.hardware.radio.ProgramSelector.Identifier[], @Nullable long[]);
-    method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(@android.hardware.radio.RadioManager.Band int, int);
-    method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(@android.hardware.radio.RadioManager.Band int, int, int);
+    ctor public ProgramSelector(int, @NonNull android.hardware.radio.ProgramSelector.Identifier, @Nullable android.hardware.radio.ProgramSelector.Identifier[], @Nullable long[]);
+    method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int);
+    method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int, int);
     method public int describeContents();
-    method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(@android.hardware.radio.ProgramSelector.IdentifierType int);
-    method public long getFirstId(@android.hardware.radio.ProgramSelector.IdentifierType int);
+    method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(int);
+    method public long getFirstId(int);
     method @NonNull public android.hardware.radio.ProgramSelector.Identifier getPrimaryId();
-    method @Deprecated @android.hardware.radio.ProgramSelector.ProgramType public int getProgramType();
+    method @Deprecated public int getProgramType();
     method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds();
     method @Deprecated @NonNull public long[] getVendorIds();
     method @NonNull public android.hardware.radio.ProgramSelector withSecondaryPreferred(@NonNull android.hardware.radio.ProgramSelector.Identifier);
@@ -5740,21 +5759,15 @@
   }
 
   public static final class ProgramSelector.Identifier implements android.os.Parcelable {
-    ctor public ProgramSelector.Identifier(@android.hardware.radio.ProgramSelector.IdentifierType int, long);
+    ctor public ProgramSelector.Identifier(int, long);
     method public int describeContents();
-    method @android.hardware.radio.ProgramSelector.IdentifierType public int getType();
+    method public int getType();
     method public long getValue();
     method public boolean isCategoryType();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
   }
 
-  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
-  }
-
-  @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
-  }
-
   public class RadioManager {
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull java.util.Set<java.lang.Integer>, @NonNull android.hardware.radio.Announcement.OnListUpdatedListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.Set<java.lang.Integer>, @NonNull android.hardware.radio.Announcement.OnListUpdatedListener);
@@ -5812,9 +5825,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandDescriptor> CREATOR;
   }
 
-  @IntDef(prefix={"BAND_"}, value={android.hardware.radio.RadioManager.BAND_INVALID, android.hardware.radio.RadioManager.BAND_AM, android.hardware.radio.RadioManager.BAND_FM, android.hardware.radio.RadioManager.BAND_AM_HD, android.hardware.radio.RadioManager.BAND_FM_HD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RadioManager.Band {
-  }
-
   public static class RadioManager.BandConfig implements android.os.Parcelable {
     method public int describeContents();
     method public int getLowerLimit();
@@ -5885,8 +5895,8 @@
     method public boolean isBackgroundScanningSupported();
     method public boolean isCaptureSupported();
     method public boolean isInitializationRequired();
-    method public boolean isProgramIdentifierSupported(@android.hardware.radio.ProgramSelector.IdentifierType int);
-    method public boolean isProgramTypeSupported(@android.hardware.radio.ProgramSelector.ProgramType int);
+    method public boolean isProgramIdentifierSupported(int);
+    method public boolean isProgramTypeSupported(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.ModuleProperties> CREATOR;
   }
@@ -10559,6 +10569,7 @@
     method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
+    method @NonNull public String getProfileLabel();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -10570,7 +10581,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
-    method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getUserSwitchability();
     method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
@@ -10630,14 +10641,11 @@
   public static final class UserManager.EnforcingUser implements android.os.Parcelable {
     method public int describeContents();
     method public android.os.UserHandle getUserHandle();
-    method @android.os.UserManager.UserRestrictionSource public int getUserRestrictionSource();
+    method public int getUserRestrictionSource();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.UserManager.EnforcingUser> CREATOR;
   }
 
-  @IntDef(flag=true, prefix={"RESTRICTION_"}, value={android.os.UserManager.RESTRICTION_NOT_SET, android.os.UserManager.RESTRICTION_SOURCE_SYSTEM, android.os.UserManager.RESTRICTION_SOURCE_DEVICE_OWNER, android.os.UserManager.RESTRICTION_SOURCE_PROFILE_OWNER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource {
-  }
-
   public abstract class Vibrator {
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
@@ -11941,13 +11949,13 @@
     method public android.service.carrier.CarrierIdentifier getCarrierIdentifier();
     method public String getIccid();
     method @Nullable public String getNickname();
-    method @android.service.euicc.EuiccProfileInfo.PolicyRule public int getPolicyRules();
-    method @android.service.euicc.EuiccProfileInfo.ProfileClass public int getProfileClass();
+    method public int getPolicyRules();
+    method public int getProfileClass();
     method public String getProfileName();
     method public String getServiceProviderName();
-    method @android.service.euicc.EuiccProfileInfo.ProfileState public int getState();
+    method public int getState();
     method @Nullable public java.util.List<android.telephony.UiccAccessRule> getUiccAccessRules();
-    method public boolean hasPolicyRule(@android.service.euicc.EuiccProfileInfo.PolicyRule int);
+    method public boolean hasPolicyRule(int);
     method public boolean hasPolicyRules();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.euicc.EuiccProfileInfo> CREATOR;
@@ -11968,23 +11976,14 @@
     method public android.service.euicc.EuiccProfileInfo.Builder setCarrierIdentifier(android.service.carrier.CarrierIdentifier);
     method public android.service.euicc.EuiccProfileInfo.Builder setIccid(String);
     method public android.service.euicc.EuiccProfileInfo.Builder setNickname(String);
-    method public android.service.euicc.EuiccProfileInfo.Builder setPolicyRules(@android.service.euicc.EuiccProfileInfo.PolicyRule int);
-    method public android.service.euicc.EuiccProfileInfo.Builder setProfileClass(@android.service.euicc.EuiccProfileInfo.ProfileClass int);
+    method public android.service.euicc.EuiccProfileInfo.Builder setPolicyRules(int);
+    method public android.service.euicc.EuiccProfileInfo.Builder setProfileClass(int);
     method public android.service.euicc.EuiccProfileInfo.Builder setProfileName(String);
     method public android.service.euicc.EuiccProfileInfo.Builder setServiceProviderName(String);
-    method public android.service.euicc.EuiccProfileInfo.Builder setState(@android.service.euicc.EuiccProfileInfo.ProfileState int);
+    method public android.service.euicc.EuiccProfileInfo.Builder setState(int);
     method public android.service.euicc.EuiccProfileInfo.Builder setUiccAccessRule(@Nullable java.util.List<android.telephony.UiccAccessRule>);
   }
 
-  @IntDef(flag=true, prefix={"POLICY_RULE_"}, value={android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DISABLE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DELETE_AFTER_DISABLING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.PolicyRule {
-  }
-
-  @IntDef(prefix={"PROFILE_CLASS_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_TESTING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_PROVISIONING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileClass {
-  }
-
-  @IntDef(prefix={"PROFILE_STATE_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_STATE_DISABLED, android.service.euicc.EuiccProfileInfo.PROFILE_STATE_ENABLED, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileState {
-  }
-
   public abstract class EuiccService extends android.app.Service {
     ctor public EuiccService();
     method public void dump(@NonNull java.io.PrintWriter);
@@ -11995,14 +11994,14 @@
     method @NonNull public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @NonNull android.os.Bundle);
     method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
     method @Deprecated public abstract int onEraseSubscriptions(int);
-    method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int);
+    method public int onEraseSubscriptions(int, int);
     method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean);
     method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean);
     method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean);
     method public abstract String onGetEid(int);
     method @NonNull public abstract android.telephony.euicc.EuiccInfo onGetEuiccInfo(int);
     method @NonNull public abstract android.service.euicc.GetEuiccProfileInfoListResult onGetEuiccProfileInfoList(int);
-    method @android.telephony.euicc.EuiccManager.OtaStatus public abstract int onGetOtaStatus(int);
+    method public abstract int onGetOtaStatus(int);
     method public abstract int onRetainSubscriptionsForFactoryReset(int);
     method public abstract void onStartOtaIfNecessary(int, android.service.euicc.EuiccService.OtaStatusChangedCallback);
     method @Deprecated public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
@@ -12266,7 +12265,7 @@
 
   public class PersistentDataBlockManager {
     method @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize();
-    method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
     method public long getMaximumDataBlockSize();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
@@ -12279,9 +12278,6 @@
     field public static final int FLASH_LOCK_UNLOCKED = 0; // 0x0
   }
 
-  @IntDef(prefix={"FLASH_LOCK_"}, value={android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNKNOWN, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_LOCKED, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNLOCKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PersistentDataBlockManager.FlashLockState {
-  }
-
 }
 
 package android.service.quicksettings {
@@ -15073,10 +15069,10 @@
 
   public class EuiccCardManager {
     method public void authenticateServer(String, String, byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
-    method public void cancelSession(String, byte[], @android.telephony.euicc.EuiccCardManager.CancelReason int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
+    method public void cancelSession(String, byte[], int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
     method public void deleteProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
     method public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
-    method public void listNotifications(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
+    method public void listNotifications(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
     method public void loadBoundProfilePackage(String, byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
     method public void prepareDownload(String, @Nullable byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
     method public void removeNotificationFromList(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
@@ -15089,9 +15085,9 @@
     method public void requestProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
     method public void requestRulesAuthTable(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccRulesAuthTable>);
     method public void requestSmdsAddress(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.String>);
-    method public void resetMemory(String, @android.telephony.euicc.EuiccCardManager.ResetOption int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
+    method public void resetMemory(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
     method public void retrieveNotification(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification>);
-    method public void retrieveNotificationList(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
+    method public void retrieveNotificationList(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
     method public void setDefaultSmdpAddress(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
     method public void setNickname(String, String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
     method @Deprecated public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
@@ -15111,12 +15107,6 @@
     field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
   }
 
-  @IntDef(prefix={"CANCEL_REASON_"}, value={android.telephony.euicc.EuiccCardManager.CANCEL_REASON_END_USER_REJECTED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_POSTPONED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_TIMEOUT, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_PPR_NOT_ALLOWED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.CancelReason {
-  }
-
-  @IntDef(flag=true, prefix={"RESET_OPTION_"}, value={android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.ResetOption {
-  }
-
   public static interface EuiccCardManager.ResultCallback<T> {
     method public void onComplete(int, T);
   }
@@ -15124,7 +15114,7 @@
   public class EuiccManager {
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(android.content.Intent, android.os.Bundle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@NonNull android.app.PendingIntent);
-    method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@android.telephony.euicc.EuiccCardManager.ResetOption int, @NonNull android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(int, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
@@ -15159,18 +15149,15 @@
     field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
   }
 
-  @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
-  }
-
   public final class EuiccNotification implements android.os.Parcelable {
-    ctor public EuiccNotification(int, String, @android.telephony.euicc.EuiccNotification.Event int, @Nullable byte[]);
+    ctor public EuiccNotification(int, String, int, @Nullable byte[]);
     method public int describeContents();
     method @Nullable public byte[] getData();
-    method @android.telephony.euicc.EuiccNotification.Event public int getEvent();
+    method public int getEvent();
     method public int getSeq();
     method public String getTargetAddr();
     method public void writeToParcel(android.os.Parcel, int);
-    field @android.telephony.euicc.EuiccNotification.Event public static final int ALL_EVENTS = 15; // 0xf
+    field public static final int ALL_EVENTS = 15; // 0xf
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.euicc.EuiccNotification> CREATOR;
     field public static final int EVENT_DELETE = 8; // 0x8
     field public static final int EVENT_DISABLE = 4; // 0x4
@@ -15178,13 +15165,10 @@
     field public static final int EVENT_INSTALL = 1; // 0x1
   }
 
-  @IntDef(flag=true, prefix={"EVENT_"}, value={android.telephony.euicc.EuiccNotification.EVENT_INSTALL, android.telephony.euicc.EuiccNotification.EVENT_ENABLE, android.telephony.euicc.EuiccNotification.EVENT_DISABLE, android.telephony.euicc.EuiccNotification.EVENT_DELETE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccNotification.Event {
-  }
-
   public final class EuiccRulesAuthTable implements android.os.Parcelable {
     method public int describeContents();
-    method public int findIndex(@android.service.euicc.EuiccProfileInfo.PolicyRule int, android.service.carrier.CarrierIdentifier);
-    method public boolean hasPolicyRuleFlag(int, @android.telephony.euicc.EuiccRulesAuthTable.PolicyRuleFlag int);
+    method public int findIndex(int, android.service.carrier.CarrierIdentifier);
+    method public boolean hasPolicyRuleFlag(int, int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.euicc.EuiccRulesAuthTable> CREATOR;
     field public static final int POLICY_RULE_FLAG_CONSENT_REQUIRED = 1; // 0x1
@@ -15196,9 +15180,6 @@
     method public android.telephony.euicc.EuiccRulesAuthTable build();
   }
 
-  @IntDef(flag=true, prefix={"POLICY_RULE_FLAG_"}, value={android.telephony.euicc.EuiccRulesAuthTable.POLICY_RULE_FLAG_CONSENT_REQUIRED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccRulesAuthTable.PolicyRuleFlag {
-  }
-
 }
 
 package android.telephony.gba {
@@ -17098,7 +17079,7 @@
   }
 
   public abstract class Window {
-    method public void addSystemFlags(@android.view.WindowManager.LayoutParams.SystemFlags int);
+    method public void addSystemFlags(int);
   }
 
   public interface WindowManager extends android.view.ViewManager {
@@ -17117,9 +17098,6 @@
     field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
   }
 
-  @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
-  }
-
 }
 
 package android.view.accessibility {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8652402..dec1ee5 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -517,6 +517,18 @@
     Methods must not throw generic exceptions (`java.lang.Throwable`)
 
 
+InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
+    Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.voice.HotwordDetectionService#getSystemService(String) parameter #0:
+    Invalid nullability on parameter `name` in method `getSystemService`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.voice.VisualQueryDetectionService#getSystemService(String) parameter #0:
+    Invalid nullability on parameter `name` in method `getSystemService`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.voice.VisualQueryDetectionService#openFileInput(String):
+    Invalid nullability on method `openFileInput` return. Overrides of unannotated super method cannot be Nullable.
+InvalidNullabilityOverride: android.service.voice.VisualQueryDetectionService#openFileInput(String) parameter #0:
+    Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+
+
 KotlinKeyword: android.app.Notification#when:
     Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords
 
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 402a718..51b8a11 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -112,6 +112,22 @@
     method @Deprecated public void requestRemoteDeviceToBecomeActiveSource(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
   }
 
+  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult {
+  }
+
+}
+
+package android.hardware.radio {
+
+  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+  }
+
+  @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
+  }
+
+  @IntDef(prefix={"BAND_"}, value={android.hardware.radio.RadioManager.BAND_INVALID, android.hardware.radio.RadioManager.BAND_AM, android.hardware.radio.RadioManager.BAND_FM, android.hardware.radio.RadioManager.BAND_AM_HD, android.hardware.radio.RadioManager.BAND_FM_HD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RadioManager.Band {
+  }
+
 }
 
 package android.media.tv {
@@ -145,6 +161,19 @@
 
 }
 
+package android.service.euicc {
+
+  @IntDef(flag=true, prefix={"POLICY_RULE_"}, value={android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DISABLE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DELETE_AFTER_DISABLING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.PolicyRule {
+  }
+
+  @IntDef(prefix={"PROFILE_CLASS_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_TESTING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_PROVISIONING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileClass {
+  }
+
+  @IntDef(prefix={"PROFILE_STATE_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_STATE_DISABLED, android.service.euicc.EuiccProfileInfo.PROFILE_STATE_ENABLED, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileState {
+  }
+
+}
+
 package android.service.notification {
 
   public abstract class NotificationListenerService extends android.app.Service {
@@ -165,6 +194,13 @@
 
 }
 
+package android.service.persistentdata {
+
+  @IntDef(prefix={"FLASH_LOCK_"}, value={android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNKNOWN, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_LOCKED, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNLOCKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PersistentDataBlockManager.FlashLockState {
+  }
+
+}
+
 package android.service.search {
 
   public abstract class SearchUiService extends android.app.Service {
@@ -213,6 +249,22 @@
 
 }
 
+package android.telephony.euicc {
+
+  @IntDef(prefix={"CANCEL_REASON_"}, value={android.telephony.euicc.EuiccCardManager.CANCEL_REASON_END_USER_REJECTED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_POSTPONED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_TIMEOUT, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_PPR_NOT_ALLOWED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.CancelReason {
+  }
+
+  @IntDef(flag=true, prefix={"RESET_OPTION_"}, value={android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.ResetOption {
+  }
+
+  @IntDef(flag=true, prefix={"EVENT_"}, value={android.telephony.euicc.EuiccNotification.EVENT_INSTALL, android.telephony.euicc.EuiccNotification.EVENT_ENABLE, android.telephony.euicc.EuiccNotification.EVENT_DISABLE, android.telephony.euicc.EuiccNotification.EVENT_DELETE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccNotification.Event {
+  }
+
+  @IntDef(flag=true, prefix={"POLICY_RULE_FLAG_"}, value={android.telephony.euicc.EuiccRulesAuthTable.POLICY_RULE_FLAG_CONSENT_REQUIRED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccRulesAuthTable.PolicyRuleFlag {
+  }
+
+}
+
 package android.telephony.ims {
 
   public interface DelegateStateCallback {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 75797ed..f4c8429 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -373,6 +373,10 @@
     method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
   }
 
+  public static class NotificationManager.Policy implements android.os.Parcelable {
+    method @FlaggedApi("android.app.modes_api") public boolean allowPriorityChannels();
+  }
+
   public final class PendingIntent implements android.os.Parcelable {
     method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
     method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
@@ -776,6 +780,25 @@
 
 }
 
+package android.app.pinner {
+
+  @FlaggedApi("android.app.pinner_service_client_api") public final class PinnedFileStat implements android.os.Parcelable {
+    ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnedFileStat(@NonNull String, long, @NonNull String);
+    method @FlaggedApi("android.app.pinner_service_client_api") public int describeContents();
+    method @FlaggedApi("android.app.pinner_service_client_api") public long getBytesPinned();
+    method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getFilename();
+    method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getGroupName();
+    method @FlaggedApi("android.app.pinner_service_client_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.app.pinner_service_client_api") @NonNull public static final android.os.Parcelable.Creator<android.app.pinner.PinnedFileStat> CREATOR;
+  }
+
+  @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient {
+    ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient();
+    method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public java.util.List<android.app.pinner.PinnedFileStat> getPinnerStats();
+  }
+
+}
+
 package android.app.prediction {
 
   public final class AppPredictor {
@@ -3603,6 +3626,8 @@
   public final class AccessibilityManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
     method public boolean hasAnyDirectConnection();
+    method @FlaggedApi("android.view.accessibility.flash_notification_system_api") public boolean startFlashNotificationSequence(@NonNull android.content.Context, int);
+    method @FlaggedApi("android.view.accessibility.flash_notification_system_api") public boolean stopFlashNotificationSequence(@NonNull android.content.Context);
   }
 
   public class AccessibilityNodeInfo implements android.os.Parcelable {
@@ -4195,8 +4220,13 @@
   public static class WindowInfosListenerForTest.WindowInfo {
     field @NonNull public final android.graphics.Rect bounds;
     field public final int displayId;
+    field public final boolean isDuplicateTouchToWallpaper;
+    field public final boolean isFocusable;
+    field public final boolean isPreventSplitting;
+    field public final boolean isTouchable;
     field public final boolean isTrustedOverlay;
     field public final boolean isVisible;
+    field public final boolean isWatchOutsideTouch;
     field @NonNull public final String name;
     field @NonNull public final android.graphics.Matrix transform;
     field @NonNull public final android.os.IBinder windowToken;
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 3a91e25..bf26bd0 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -511,6 +511,16 @@
     Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
 
 
+InvalidNullabilityOverride: android.window.WindowProviderService#getSystemService(String) parameter #0:
+    Invalid nullability on parameter `name` in method `getSystemService`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.window.WindowProviderService#onConfigurationChanged(android.content.res.Configuration) parameter #0:
+    Invalid nullability on parameter `configuration` in method `onConfigurationChanged`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.window.WindowProviderService#registerComponentCallbacks(android.content.ComponentCallbacks) parameter #0:
+    Invalid nullability on parameter `callback` in method `registerComponentCallbacks`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.window.WindowProviderService#unregisterComponentCallbacks(android.content.ComponentCallbacks) parameter #0:
+    Invalid nullability on parameter `callback` in method `unregisterComponentCallbacks`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+
+
 KotlinKeyword: android.app.Notification#when:
     Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords
 
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 48cafc5..fb1e16a 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -413,6 +413,10 @@
     backend: {
         rust: {
             enabled: true,
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.virt",
+            ],
         },
     },
 }
@@ -542,6 +546,15 @@
     ],
 }
 
+// PackageManager common
+filegroup {
+    name: "framework-pm-common-shared-srcs",
+    srcs: [
+        "com/android/server/pm/pkg/AndroidPackage.java",
+        "com/android/server/pm/pkg/AndroidPackageSplit.java",
+    ],
+}
+
 java_library {
     name: "protolog-lib",
     platform_apis: true,
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index c376eae..76735b6 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.java
@@ -38,6 +38,7 @@
  * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it
  * suitable for use as the key of a {@link java.util.Map}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Account implements Parcelable {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private static final String TAG = "Account";
@@ -104,18 +105,27 @@
         if (accessId != null) {
             synchronized (sAccessedAccounts) {
                 if (sAccessedAccounts.add(this)) {
-                    try {
-                        IAccountManager accountManager = IAccountManager.Stub.asInterface(
-                                ServiceManager.getService(Context.ACCOUNT_SERVICE));
-                        accountManager.onAccountAccessed(accessId);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error noting account access", e);
-                    }
+                    onAccountAccessed(accessId);
                 }
             }
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static void onAccountAccessed(String accessId) {
+        try {
+            IAccountManager accountManager = IAccountManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ACCOUNT_SERVICE));
+            accountManager.onAccountAccessed(accessId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error noting account access", e);
+        }
+    }
+
+    private static void onAccountAccessed$ravenwood(String accessId) {
+        // No AccountManager to communicate with; ignored
+    }
+
     /** @hide */
     public String getAccessId() {
         return accessId;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ed18d81..00432dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -20,12 +20,11 @@
 import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
-
 import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
-
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.AnimRes;
@@ -6352,6 +6351,10 @@
      */
     public boolean startActivityIfNeeded(@RequiresPermission @NonNull Intent intent,
             int requestCode, @Nullable Bundle options) {
+        if (Instrumentation.DEBUG_START_ACTIVITY) {
+            Log.d("Instrumentation", "startActivity: intent=" + intent
+                    + " requestCode=" + requestCode + " options=" + options, new Throwable());
+        }
         if (mParent == null) {
             int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
             try {
@@ -9435,6 +9438,15 @@
         ActivityClient.getInstance().enableTaskLocaleOverride(mToken);
     }
 
+    /**
+     * Request ActivityRecordInputSink to enable or disable blocking input events.
+     * @hide
+     */
+    @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+    public void setActivityRecordInputSinkEnabled(boolean enabled) {
+        ActivityClient.getInstance().setActivityRecordInputSinkEnabled(mToken, enabled);
+    }
+
     class HostCallbacks extends FragmentHostCallback<Activity> {
         public HostCallbacks() {
             super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index b35e87b..b8bd030 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.ComponentName;
@@ -614,6 +616,15 @@
         }
     }
 
+    @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+    void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+        try {
+            getActivityClientController().setActivityRecordInputSinkEnabled(activityToken, enabled);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
      *
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c136db6..8af1216 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -56,7 +56,7 @@
 import android.app.backup.BackupAnnotations.OperationType;
 import android.app.compat.CompatChanges;
 import android.app.sdksandbox.sandboxactivity.ActivityContextInfo;
-import android.app.sdksandbox.sandboxactivity.ActivityContextInfoProvider;
+import android.app.sdksandbox.sandboxactivity.SdkSandboxActivityAuthority;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -232,6 +232,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.org.conscrypt.TrustedCertificateStore;
 import com.android.server.am.MemInfoDumpProto;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.AppSpecializationHooks;
@@ -3713,7 +3714,13 @@
         final ArrayList<ResultInfo> list = new ArrayList<>();
         list.add(new ResultInfo(id, requestCode, resultCode, data));
         final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
-        clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list));
+        final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+                activityToken, list);
+        if (Flags.bundleClientTransactionFlag()) {
+            clientTransaction.addTransactionItem(activityResultItem);
+        } else {
+            clientTransaction.addCallback(activityResultItem);
+        }
         try {
             mAppThread.scheduleTransaction(clientTransaction);
         } catch (RemoteException e) {
@@ -3795,8 +3802,10 @@
                     r.activityInfo.targetActivity);
         }
 
-        boolean isSandboxActivityContext = sandboxActivitySdkBasedContext()
-                && r.intent.isSandboxActivity(mSystemContext);
+        boolean isSandboxActivityContext =
+                sandboxActivitySdkBasedContext()
+                        && SdkSandboxActivityAuthority.isSdkSandboxActivity(
+                                mSystemContext, r.intent);
         boolean isSandboxedSdkContextUsed = false;
         ContextImpl activityBaseContext;
         if (isSandboxActivityContext) {
@@ -4041,11 +4050,12 @@
      */
     @Nullable
     private ContextImpl createBaseContextForSandboxActivity(@NonNull ActivityClientRecord r) {
-        ActivityContextInfoProvider contextInfoProvider = ActivityContextInfoProvider.getInstance();
+        SdkSandboxActivityAuthority sdkSandboxActivityAuthority =
+                SdkSandboxActivityAuthority.getInstance();
 
         ActivityContextInfo contextInfo;
         try {
-            contextInfo = contextInfoProvider.getActivityContextInfo(r.intent);
+            contextInfo = sdkSandboxActivityAuthority.getActivityContextInfo(r.intent);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Passed intent does not match an expected sandbox activity", e);
             return null;
@@ -4489,16 +4499,26 @@
 
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token,
+        final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
                 r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
-                /* dontReport */ false, /* autoEnteringPip */ false));
+                /* dontReport */ false, /* autoEnteringPip */ false);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(pauseActivityItem);
+        } else {
+            transaction.setLifecycleStateRequest(pauseActivityItem);
+        }
         executeTransaction(transaction);
     }
 
     private void scheduleResume(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token,
-                /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(resumeActivityItem);
+        } else {
+            transaction.setLifecycleStateRequest(resumeActivityItem);
+        }
         executeTransaction(transaction);
     }
 
@@ -6089,8 +6109,13 @@
                 TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
         // Schedule the transaction.
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
-        transaction.addCallback(activityRelaunchItem);
-        transaction.setLifecycleStateRequest(lifecycleRequest);
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(activityRelaunchItem);
+            transaction.addTransactionItem(lifecycleRequest);
+        } else {
+            transaction.addCallback(activityRelaunchItem);
+            transaction.setLifecycleStateRequest(lifecycleRequest);
+        }
         executeTransaction(transaction);
     }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b74b075..c5e132f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -9927,10 +9927,11 @@
             if (i != firstInteresting) {
                 sb.append('\n');
             }
-            if (!sFullLog && sb.length() + trace[i].toString().length() > 600) {
+            final String traceString = trace[i].toString();
+            if (!sFullLog && sb.length() + traceString.length() > 600) {
                 break;
             }
-            sb.append(trace[i]);
+            sb.append(traceString);
         }
 
         return sb.toString();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ca6d8df..87c86df 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -80,6 +80,8 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.res.ApkAssets;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -144,6 +146,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /** @hide */
 public class ApplicationPackageManager extends PackageManager {
@@ -2931,6 +2934,15 @@
     }
 
     @Override
+    public String getSuspendingPackage(String suspendedPackage) {
+        try {
+            return mPM.getSuspendingPackage(suspendedPackage, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         try {
             return mPM.isPackageSuspendedForUser(packageName, userId);
@@ -4024,4 +4036,34 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Override
+    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+            @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+        Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+        Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
+        try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+            return parserFunction.apply(xmlResourceParser);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to get the android manifest parser", e);
+            throw e;
+        }
+    }
+
+    private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+            throws IOException {
+        ApkAssets apkAssets = null;
+        try {
+            apkAssets = ApkAssets.loadFromPath(apkFilePath);
+            return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
+        } finally {
+            if (apkAssets != null) {
+                try {
+                    apkAssets.close();
+                } catch (Throwable ignored) {
+                    Log.w(TAG, "Failed to close apkAssets", ignored);
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index a7b29aa..d935449 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -36,7 +36,7 @@
 import java.util.Objects;
 
 /**
- * Rule instance information for zen mode.
+ * Rule instance information for a zen (aka DND or Attention Management) mode.
  */
 public final class AutomaticZenRule implements Parcelable {
     /* @hide */
@@ -45,7 +45,9 @@
     private static final int DISABLED = 0;
 
     /**
-     * Rule is of an unknown type. This is the default value if not provided by the owning app.
+     * Rule is of an unknown type. This is the default value if not provided by the owning app,
+     * and the value returned if the true type was added in an API level lower than the calling
+     * app's targetSdk.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int TYPE_UNKNOWN = -1;
@@ -378,7 +380,7 @@
      * Gets the type of the rule.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public int getType() {
+    public @Type int getType() {
         return mType;
     }
 
@@ -594,7 +596,7 @@
         private ComponentName mOwner;
         private Uri mConditionId;
         private int mInterruptionFilter;
-        private boolean mEnabled;
+        private boolean mEnabled = true;
         private ComponentName mConfigurationActivity = null;
         private ZenPolicy mPolicy = null;
         private ZenDeviceEffects mDeviceEffects = null;
@@ -627,38 +629,63 @@
             mConditionId = conditionId;
         }
 
+        /**
+         * Sets the name of this rule.
+         */
         public @NonNull Builder setName(@NonNull String name) {
             mName = name;
             return this;
         }
 
+        /**
+         * Sets the component (service or activity) that owns this rule.
+         */
         public @NonNull Builder setOwner(@Nullable ComponentName owner) {
             mOwner = owner;
             return this;
         }
 
+        /**
+         * Sets the representation of the state that causes this rule to become active.
+         */
         public @NonNull Builder setConditionId(@NonNull Uri conditionId) {
             mConditionId = conditionId;
             return this;
         }
 
+        /**
+         * Sets the interruption filter that is applied when this rule is active.
+         */
         public @NonNull Builder setInterruptionFilter(
                 @InterruptionFilter int interruptionFilter) {
             mInterruptionFilter = interruptionFilter;
             return this;
         }
 
+        /**
+         * Enables this rule. Rules are enabled by default.
+         */
         public @NonNull Builder setEnabled(boolean enabled) {
             mEnabled = enabled;
             return this;
         }
 
+        /**
+         * Sets the configuration activity - an activity that handles
+         * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more
+         * information about this rule and/or allows them to configure it. This is required to be
+         * non-null for rules that are not backed by a
+         * {@link android.service.notification.ConditionProviderService}.
+         */
         public @NonNull Builder setConfigurationActivity(
                 @Nullable ComponentName configurationActivity) {
             mConfigurationActivity = configurationActivity;
             return this;
         }
 
+        /**
+         * Sets the zen policy.
+         */
         public @NonNull Builder setZenPolicy(@Nullable ZenPolicy policy) {
             mPolicy = policy;
             return this;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 08c18c8..4f8e8dd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3482,7 +3482,8 @@
         mResources = r;
 
         // only do this if the user already has more than one preferred locale
-        if (r.getConfiguration().getLocales().size() > 1) {
+        if (android.content.res.Flags.defaultLocale()
+                && r.getConfiguration().getLocales().size() > 1) {
             LocaleConfig lc = getUserId() < 0
                     ? LocaleConfig.fromContextIgnoringOverride(this)
                     : new LocaleConfig(this);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index a3c5e1c..7370fc3 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -191,4 +191,14 @@
      */
     boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken,
             in IBinder taskFragmentToken);
+
+    /**
+     * Enable or disable ActivityRecordInputSink to block input events.
+     *
+     * @param token The token for the activity that requests to toggle.
+     * @param enabled Whether the input evens are blocked by ActivityRecordInputSink.
+     */
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.INTERNAL_SYSTEM_WINDOW)")
+    oneway void setActivityRecordInputSinkEnabled(in IBinder activityToken, boolean enabled);
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index df6badc..d540748 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -328,7 +328,7 @@
      * A splash screen view has copied.
      */
     void onSplashScreenViewCopyFinished(int taskId,
-            in SplashScreenView.SplashScreenViewParcelable material);
+            in @nullable SplashScreenView.SplashScreenViewParcelable material);
 
     /**
      * When the Picture-in-picture state has changed.
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 357ee0a..2162e3a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -43,6 +43,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.TestLooperManager;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -67,6 +68,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.StringJoiner;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -100,6 +102,10 @@
 
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
+    // If set, will print the stack trace for activity starts within the process
+    static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
+            SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
+
     /**
      * @hide
      */
@@ -577,6 +583,9 @@
      */
     @NonNull
     public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: intent=" + intent + " options=" + options, new Throwable());
+        }
         validateNotAppThread();
 
         final Activity activity;
@@ -1891,6 +1900,10 @@
     public ActivityResult execStartActivity(
             Context who, IBinder contextThread, IBinder token, Activity target,
             Intent intent, int requestCode, Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: who=" + who + " source=" + target + " intent=" + intent
+                    + " requestCode=" + requestCode + " options=" + options, new Throwable());
+        }
         Objects.requireNonNull(intent);
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         Uri referrer = target != null ? target.onProvideReferrer() : null;
@@ -1971,6 +1984,14 @@
     public int execStartActivitiesAsUser(Context who, IBinder contextThread,
             IBinder token, Activity target, Intent[] intents, Bundle options,
             int userId) {
+        if (DEBUG_START_ACTIVITY) {
+            StringJoiner joiner = new StringJoiner(", ");
+            for (Intent i : intents) {
+                joiner.add(i.toString());
+            }
+            Log.d(TAG, "startActivities: who=" + who + " source=" + target + " userId=" + userId
+                    + " intents=[" + joiner + "] options=" + options, new Throwable());
+        }
         Objects.requireNonNull(intents);
         for (int i = intents.length - 1; i >= 0; i--) {
             Objects.requireNonNull(intents[i]);
@@ -2055,6 +2076,11 @@
     public ActivityResult execStartActivity(
         Context who, IBinder contextThread, IBinder token, String target,
         Intent intent, int requestCode, Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: who=" + who + " target=" + target
+                    + " intent=" + intent + " requestCode=" + requestCode
+                    + " options=" + options, new Throwable());
+        }
         Objects.requireNonNull(intent);
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (isSdkSandboxAllowedToStartActivities()) {
@@ -2130,6 +2156,11 @@
     public ActivityResult execStartActivity(
             Context who, IBinder contextThread, IBinder token, String resultWho,
             Intent intent, int requestCode, Bundle options, UserHandle user) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: who=" + who + " user=" + user + " intent=" + intent
+                    + " requestCode=" + requestCode + " resultWho=" + resultWho
+                    + " options=" + options, new Throwable());
+        }
         Objects.requireNonNull(intent);
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (isSdkSandboxAllowedToStartActivities()) {
@@ -2184,6 +2215,12 @@
             Context who, IBinder contextThread, IBinder token, Activity target,
             Intent intent, int requestCode, Bundle options,
             boolean ignoreTargetSecurity, int userId) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: who=" + who + " source=" + target + " userId=" + userId
+                    + " intent=" + intent + " requestCode=" + requestCode
+                    + " ignoreTargetSecurity=" + ignoreTargetSecurity + " options=" + options,
+                    new Throwable());
+        }
         Objects.requireNonNull(intent);
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (isSdkSandboxAllowedToStartActivities()) {
@@ -2239,6 +2276,10 @@
     public void execStartActivityFromAppTask(
             Context who, IBinder contextThread, IAppTask appTask,
             Intent intent, Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(TAG, "startActivity: who=" + who + " intent=" + intent
+                    + " options=" + options, new Throwable());
+        }
         Objects.requireNonNull(intent);
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (isSdkSandboxAllowedToStartActivities()) {
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 369a781..b2be27f 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -201,6 +201,7 @@
 
         String defaultLocale = null;
         if (android.content.res.Flags.defaultLocale()) {
+            // Read the defaultLocale attribute of the LocaleConfig element
             TypedArray att = res.obtainAttributes(
                     attrs, com.android.internal.R.styleable.LocaleConfig);
             defaultLocale = att.getString(
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 51c937d..d23b16d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -51,6 +51,7 @@
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
@@ -1247,6 +1248,23 @@
     }
 
     /**
+     * Returns true if users can independently and fully manage {@link AutomaticZenRule} rules. This
+     * includes the ability to independently activate/deactivate rules and overwrite/freeze the
+     * behavior (policy) of the rule when activated.
+     * <p>
+     * If this method returns true, calls to
+     * {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer
+     * rule management to system settings/uis via
+     * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public boolean areAutomaticZenRulesUserManaged() {
+        // modes ui is dependent on modes api
+        return Flags.modesApi() && Flags.modesUi();
+    }
+
+
+    /**
      * Returns AutomaticZenRules owned by the caller.
      *
      * <p>
@@ -2040,6 +2058,14 @@
         public static final int STATE_CHANNELS_BYPASSING_DND = 1 << 0;
 
         /**
+         * Whether the policy indicates that even priority channels are NOT permitted to bypass DND.
+         * Note that this state explicitly marks the "disallow" state because the default behavior
+         * is to allow priority channels to break through.
+         * @hide
+         */
+        public static final int STATE_PRIORITY_CHANNELS_BLOCKED = 1 << 1;
+
+        /**
          * @hide
          */
         public static final int STATE_UNSET = -1;
@@ -2254,20 +2280,34 @@
 
         @Override
         public String toString() {
-            return "NotificationManager.Policy["
-                    + "priorityCategories=" + priorityCategoriesToString(priorityCategories)
-                    + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders)
-                    + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
-                    + ",priorityConvSenders="
-                    + conversationSendersToString(priorityConversationSenders)
-                    + ",suppressedVisualEffects="
-                    + suppressedEffectsToString(suppressedVisualEffects)
-                    + ",areChannelsBypassingDnd=" + (state == STATE_UNSET
-                        ? "unset"
-                        : ((state & STATE_CHANNELS_BYPASSING_DND) != 0)
-                                ? "true"
-                                : "false")
-                    + "]";
+            StringBuilder sb = new StringBuilder().append("NotificationManager.Policy[")
+                    .append("priorityCategories=")
+                    .append(priorityCategoriesToString(priorityCategories))
+                    .append(",priorityCallSenders=")
+                    .append(prioritySendersToString(priorityCallSenders))
+                    .append(",priorityMessageSenders=")
+                    .append(prioritySendersToString(priorityMessageSenders))
+                    .append(",priorityConvSenders=")
+                    .append(conversationSendersToString(priorityConversationSenders))
+                    .append(",suppressedVisualEffects=")
+                    .append(suppressedEffectsToString(suppressedVisualEffects));
+            if (Flags.modesApi()) {
+                sb.append(",hasPriorityChannels=");
+            } else {
+                sb.append(",areChannelsBypassingDnd=");
+            }
+            sb.append((state == STATE_UNSET
+                    ? "unset"
+                    : ((state & STATE_CHANNELS_BYPASSING_DND) != 0)
+                            ? "true"
+                            : "false"));
+            if (Flags.modesApi()) {
+                sb.append(",allowPriorityChannels=")
+                        .append((state == STATE_UNSET
+                                ? "unset"
+                                : (allowPriorityChannels() ? "true" : "false")));
+            }
+            return sb.append("]").toString();
         }
 
         /** @hide */
@@ -2538,6 +2578,35 @@
             return (suppressedVisualEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0;
         }
 
+        /** @hide **/
+        @FlaggedApi(Flags.FLAG_MODES_API)
+        @TestApi // so CTS tests can read this state without having to use implementation detail
+        public boolean allowPriorityChannels() {
+            if (state == STATE_UNSET) {
+                return true; // default
+            }
+            return (state & STATE_PRIORITY_CHANNELS_BLOCKED) == 0;
+        }
+
+        /** @hide */
+        @FlaggedApi(Flags.FLAG_MODES_API)
+        public boolean hasPriorityChannels() {
+            return (state & STATE_CHANNELS_BYPASSING_DND) != 0;
+        }
+
+        /** @hide **/
+        @FlaggedApi(Flags.FLAG_MODES_API)
+        public static int policyState(boolean hasPriorityChannels, boolean allowPriorityChannels) {
+            int state = 0;
+            if (hasPriorityChannels) {
+                state |= STATE_CHANNELS_BYPASSING_DND;
+            }
+            if (!allowPriorityChannels) {
+                state |= STATE_PRIORITY_CHANNELS_BLOCKED;
+            }
+            return state;
+        }
+
         /**
          * returns a deep copy of this policy
          * @hide
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index cc56a1c..772b0b4 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -72,6 +72,7 @@
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
 per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
+per-file notification.aconfig = file:/packages/SystemUI/OWNERS
 
 # PackageManager
 per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
@@ -84,6 +85,9 @@
 per-file IInstantAppResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS
 per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS
 
+# Pinner
+per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
+
 # ResourcesManager
 per-file ResourcesManager.java = file:RESOURCES_OWNERS
 
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
index 8816109..145efa9 100644
--- a/core/java/android/app/RemoteInput.java
+++ b/core/java/android/app/RemoteInput.java
@@ -429,7 +429,7 @@
         if (clipDataIntent == null) {
             return null;
         }
-        return clipDataIntent.getExtras().getParcelable(EXTRA_RESULTS_DATA, android.os.Bundle.class);
+        return clipDataIntent.getParcelableExtra(EXTRA_RESULTS_DATA, android.os.Bundle.class);
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 6a03c17..ce1d43d 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,6 +180,11 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+        synchronized (mLock) {
+            throwIfCalledByNotTrustedUidLocked();
+            throwIfShutdownLocked();
+            throwIfNotConnectedLocked();
+        }
         mAccessibilityManager.injectInputEventToInputFilter(event);
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3ee9d692..fc3a906 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -17164,6 +17164,7 @@
      *
      * @hide
      */
+    @UnsupportedAppUsage
     public boolean isOnboardingBugreportV2FlagEnabled() {
         return onboardingBugreportV2Enabled();
     }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index bbd07b8..35ce102 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -48,3 +48,10 @@
   description: "Update DumpSys to include information about migrated APIs in DPE"
   bug: "304999634"
 }
+
+flag {
+  name: "quiet_mode_credential_bug_fix"
+  namespace: "enterprise"
+  description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
+  bug: "293441361"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index d9b521f..fb0edb9 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+  name: "modes_ui"
+  namespace: "systemui"
+  description: "This flag controls new and updated DND UIs; dependent on flag modes_api"
+  bug: "270703654"
+}
+
+flag {
   name: "api_tvextender"
   namespace: "systemui"
   description: "Guards new android.app.Notification.TvExtender api"
@@ -15,3 +22,17 @@
   is_fixed_read_only: true
 }
 
+flag {
+  name: "lifetime_extension_refactor"
+  namespace: "systemui"
+  description: "Enables moving notification lifetime extension management from SystemUI to "
+      "Notification Manager Service"
+  bug: "299448097"
+}
+
+flag {
+  name: "visit_risky_uris"
+  namespace: "systemui"
+  description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
+  bug: "281044385"
+}
diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig
new file mode 100644
index 0000000..b60ad9e
--- /dev/null
+++ b/core/java/android/app/pinner-client.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+     namespace: "system_performance"
+     name: "pinner_service_client_api"
+     description: "Control exposing PinnerService APIs."
+     bug: "307594624"
+}
\ No newline at end of file
diff --git a/core/java/android/app/pinner/IPinnerService.aidl b/core/java/android/app/pinner/IPinnerService.aidl
new file mode 100644
index 0000000..e5d0a05
--- /dev/null
+++ b/core/java/android/app/pinner/IPinnerService.aidl
@@ -0,0 +1,12 @@
+package android.app.pinner;
+
+import android.app.pinner.PinnedFileStat;
+
+/**
+ * Interface for processes to communicate with system's PinnerService.
+ * @hide
+ */
+interface IPinnerService {
+    @EnforcePermission("DUMP")
+    List<PinnedFileStat> getPinnerStats();
+}
\ No newline at end of file
diff --git a/core/java/android/app/pinner/OWNERS b/core/java/android/app/pinner/OWNERS
new file mode 100644
index 0000000..3e3fa66
--- /dev/null
+++ b/core/java/android/app/pinner/OWNERS
@@ -0,0 +1,10 @@
+carmenjackson@google.com
+dualli@google.com
+edgararriaga@google.com
+kevinjeon@google.com
+philipcuadra@google.com
+shombert@google.com
+timmurray@google.com
+wessam@google.com
+jdduke@google.com
+shayba@google.com
\ No newline at end of file
diff --git a/core/java/android/app/pinner/PinnedFileStat.aidl b/core/java/android/app/pinner/PinnedFileStat.aidl
new file mode 100644
index 0000000..44217cf
--- /dev/null
+++ b/core/java/android/app/pinner/PinnedFileStat.aidl
@@ -0,0 +1,3 @@
+package android.app.pinner;
+
+parcelable PinnedFileStat;
\ No newline at end of file
diff --git a/core/java/android/app/pinner/PinnedFileStat.java b/core/java/android/app/pinner/PinnedFileStat.java
new file mode 100644
index 0000000..2e36330
--- /dev/null
+++ b/core/java/android/app/pinner/PinnedFileStat.java
@@ -0,0 +1,133 @@
+/*
+ * 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.app.pinner;
+
+import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+public final class PinnedFileStat implements Parcelable {
+    private String filename;
+    private long bytesPinned;
+    private String groupName;
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public long getBytesPinned() {
+        return bytesPinned;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public @NonNull String getFilename() {
+        return filename;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public @NonNull String getGroupName() {
+        return groupName;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public PinnedFileStat(@NonNull String filename, long bytesPinned, @NonNull String groupName) {
+        this.filename = filename;
+        this.bytesPinned = bytesPinned;
+        this.groupName = groupName;
+    }
+
+    private PinnedFileStat(Parcel source) {
+        readFromParcel(source);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(filename);
+        dest.writeLong(bytesPinned);
+        dest.writeString8(groupName);
+    }
+
+    private void readFromParcel(@NonNull Parcel source) {
+        filename = source.readString8();
+        bytesPinned = source.readLong();
+        groupName = source.readString8();
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public static final @NonNull Creator<PinnedFileStat> CREATOR = new Creator<>() {
+        /**
+         * @hide
+         */
+        @TestApi
+        @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+        @Override
+        public PinnedFileStat createFromParcel(Parcel source) {
+            return new PinnedFileStat(source);
+        }
+
+        /**
+         * @hide
+         */
+        @TestApi
+        @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+        @Override
+        public PinnedFileStat[] newArray(int size) {
+            return new PinnedFileStat[size];
+        }
+    };
+}
diff --git a/core/java/android/app/pinner/PinnerServiceClient.java b/core/java/android/app/pinner/PinnerServiceClient.java
new file mode 100644
index 0000000..8b7c6cc
--- /dev/null
+++ b/core/java/android/app/pinner/PinnerServiceClient.java
@@ -0,0 +1,77 @@
+/*
+ * 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.app.pinner;
+
+import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.pinner.IPinnerService;
+import android.app.pinner.PinnedFileStat;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Expose PinnerService as an interface to apps.
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+public class PinnerServiceClient {
+    private static String TAG = "PinnerServiceClient";
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public PinnerServiceClient() {}
+
+    /**
+     * Obtain the pinned file stats used for testing infrastructure.
+     * @return List of pinned files or an empty list if failed to retrieve them.
+     * @throws RuntimeException on failure to retrieve stats.
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API)
+    public @NonNull List<PinnedFileStat> getPinnerStats() {
+        IBinder binder = ServiceManager.getService("pinner");
+        if (binder == null) {
+            Slog.w(TAG,
+                    "Failed to retrieve PinnerService. A common failure reason is due to a lack of selinux permissions.");
+            return new ArrayList<>();
+        }
+        IPinnerService pinnerService = IPinnerService.Stub.asInterface(binder);
+        if (pinnerService == null) {
+            Slog.w(TAG, "Failed to cast PinnerService.");
+            return new ArrayList<>();
+        }
+        List<PinnedFileStat> stats;
+        try {
+            stats = pinnerService.getPinnerStats();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed to retrieve stats from PinnerService");
+        }
+        return stats;
+    }
+}
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index 06bff5d..48db18f 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -59,7 +59,7 @@
     }
 
     @Override
-    boolean isActivityLifecycleItem() {
+    public boolean isActivityLifecycleItem() {
         return true;
     }
 
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7c34cde..5e55268 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -75,7 +75,6 @@
     /**
      * Adds a message to the end of the sequence of transaction items.
      * @param item A single message that can contain a client activity/window request/callback.
-     * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
      */
     public void addTransactionItem(@NonNull ClientTransactionItem item) {
         if (mTransactionItems == null) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index f94e22d..a8d61db 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -75,7 +75,7 @@
     /**
      * Whether this is a {@link ActivityLifecycleItem}.
      */
-    boolean isActivityLifecycleItem() {
+    public boolean isActivityLifecycleItem() {
         return false;
     }
 
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index dfbccb4..475c6fb 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -235,7 +235,9 @@
      *   Configuration - ActivityResult - Configuration - ActivityResult
      * index 1 will be returned, because ActivityResult request on position 1 will be the last
      * request that moves activity to the RESUMED state where it will eventually end.
+     * @deprecated to be removed with {@link TransactionExecutor#executeCallbacks}.
      */
+    @Deprecated
     static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
         if (callbacks == null || callbacks.isEmpty()
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 49a4467..0f7a070 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -10,7 +9,18 @@
       ]
     },
     {
-      "name": "FrameworksServicesTests",
+      "name": "CtsTimeTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksTimeServicesTests",
       "options": [
         {
           "include-filter": "com.android.server.timezonedetector."
@@ -19,14 +29,6 @@
           "include-filter": "com.android.server.timedetector."
         }
       ]
-    },
-    {
-      "name": "CtsTimeTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
     }
   ]
 }
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
index c050a55..43dd82f 100644
--- a/core/java/android/app/timedetector/TEST_MAPPING
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -10,14 +9,6 @@
       ]
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.timedetector."
-        }
-      ]
-    },
-    {
       "name": "CtsTimeTestCases",
       "options": [
         {
@@ -25,5 +16,16 @@
         }
       ]
     }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksTimeServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timedetector."
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index 46656d1..2be5614 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -10,14 +9,6 @@
       ]
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.timezonedetector."
-        }
-      ]
-    },
-    {
       "name": "CtsTimeTestCases",
       "options": [
         {
@@ -25,5 +16,16 @@
         }
       ]
     }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksTimeServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timezonedetector."
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index ebd5d64..cf19178 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -22,6 +22,7 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEventsQuery;
 import android.content.pm.ParceledListSlice;
+import android.os.PersistableBundle;
 
 /**
  * System private API for talking with the UsageStatsManagerService.
@@ -77,6 +78,8 @@
             String callingPackage);
     void reportUsageStop(in IBinder activity, String token, String callingPackage);
     void reportUserInteraction(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)")
+    void reportUserInteractionWithBundle(String packageName, int userId, in PersistableBundle eventExtras);
     int getUsageSource();
     void forceUsageSourceSettingRead();
     long getLastTimeAnyComponentUsed(String packageName, String callingPackage);
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
index 016d97f..7bc6cb9 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -48,13 +48,16 @@
 
     private List<Event> mList;
 
-    public ParcelableUsageEventList(List<Event> list) {
+    public ParcelableUsageEventList(@NonNull List<Event> list) {
+        if (list == null) {
+            throw new IllegalArgumentException("Empty list");
+        }
         mList = list;
     }
 
     private ParcelableUsageEventList(Parcel in) {
         final int N = in.readInt();
-        mList = new ArrayList<>();
+        mList = new ArrayList<>(N);
         if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
         if (N <= 0) {
             return;
@@ -220,6 +223,7 @@
         event.mContentAnnotations = null;
         event.mNotificationChannelId = null;
         event.mLocusId = null;
+        event.mExtras = null;
 
         switch (event.mEventType) {
             case Event.CONFIGURATION_CHANGE -> {
@@ -234,6 +238,11 @@
             case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt();
             case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString();
             case Event.LOCUS_ID_SET -> event.mLocusId = in.readString();
+            case Event.USER_INTERACTION -> {
+                if (in.readInt() != 0) {
+                    event.mExtras = in.readPersistableBundle(getClass().getClassLoader());
+                }
+            }
         }
         event.mFlags = in.readInt();
 
@@ -260,6 +269,14 @@
             case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason);
             case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId);
             case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId);
+            case Event.USER_INTERACTION -> {
+                if (event.mExtras != null) {
+                    dest.writeInt(1);
+                    dest.writePersistableBundle(event.mExtras);
+                } else {
+                    dest.writeInt(0);
+                }
+            }
         }
         dest.writeInt(event.mFlags);
     }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 1eb452c..0ae00cd 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,7 +16,9 @@
 package android.app.usage;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -24,9 +26,12 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -35,6 +40,7 @@
  * from which to read {@link android.app.usage.UsageEvents.Event} objects.
  */
 public final class UsageEvents implements Parcelable {
+    private static final String TAG = "UsageEvents";
 
     /** @hide */
     public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app";
@@ -548,6 +554,22 @@
         public int mLocusIdToken = UNASSIGNED_TOKEN;
 
         /** @hide */
+        public PersistableBundle mExtras = null;
+
+        /** @hide */
+        public static class UserInteractionEventExtrasToken {
+            public int mCategoryToken = UNASSIGNED_TOKEN;
+            public int mActionToken = UNASSIGNED_TOKEN;
+
+            public UserInteractionEventExtrasToken() {
+                // Do nothing.
+            }
+        }
+
+        /** @hide */
+        public UserInteractionEventExtrasToken mUserInteractionExtrasToken = null;
+
+        /** @hide */
         @EventFlags
         public int mFlags;
 
@@ -647,6 +669,21 @@
         }
 
         /**
+         * Retrieves a map of extended data from the event if the event is of type
+         * {@link #USER_INTERACTION}.
+         *
+         * @return the map of all extras that associated with the reported user interaction
+         *         event. The returned {@link PersistableBundle} will contain the extras
+         *         {@link UsageStatsManager#EXTRA_EVENT_CATEGORY} and
+         *         {@link UsageStatsManager#EXTRA_EVENT_ACTION}. {@link PersistableBundle#EMPTY}
+         *         will be returned if the details are not available.
+         */
+        @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+        public @NonNull PersistableBundle getExtras() {
+            return mExtras == null ? PersistableBundle.EMPTY : mExtras;
+        }
+
+        /**
          * Returns a {@link Configuration} for this event if the event is of type
          * {@link #CONFIGURATION_CHANGE}, otherwise it returns null.
          */
@@ -744,6 +781,7 @@
             mBucketAndReason = orig.mBucketAndReason;
             mNotificationChannelId = orig.mNotificationChannelId;
             mLocusId = orig.mLocusId;
+            mExtras = orig.mExtras;
         }
     }
 
@@ -786,10 +824,20 @@
     }
 
     private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+        mEventCount = in.readInt();
         mIndex = in.readInt();
-        mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
-            ParcelableUsageEventList.class).getList();
-        mEventCount = mEventsToWrite.size();
+        ParcelableUsageEventList slice = in.readParcelable(getClass().getClassLoader(),
+                ParcelableUsageEventList.class);
+        if (slice != null) {
+            mEventsToWrite = slice.getList();
+        } else {
+            mEventsToWrite = new ArrayList<>();
+        }
+
+        if (mEventCount != mEventsToWrite.size()) {
+            Log.w(TAG, "Partial usage event list received: " + mEventCount + " != "
+                    + mEventsToWrite.size());
+        }
     }
 
     private void readUsageEventsFromParcelWithBlob(Parcel in) {
@@ -974,6 +1022,14 @@
             case Event.LOCUS_ID_SET:
                 p.writeString(event.mLocusId);
                 break;
+            case Event.USER_INTERACTION:
+                if (event.mExtras != null) {
+                    p.writeInt(1);
+                    p.writePersistableBundle(event.mExtras);
+                } else {
+                    p.writeInt(0);
+                }
+                break;
         }
         p.writeInt(event.mFlags);
     }
@@ -1023,6 +1079,7 @@
         eventOut.mContentAnnotations = null;
         eventOut.mNotificationChannelId = null;
         eventOut.mLocusId = null;
+        eventOut.mExtras = null;
 
         switch (eventOut.mEventType) {
             case Event.CONFIGURATION_CHANGE:
@@ -1046,6 +1103,11 @@
             case Event.LOCUS_ID_SET:
                 eventOut.mLocusId = p.readString();
                 break;
+            case Event.USER_INTERACTION:
+                if (p.readInt() != 0) {
+                    eventOut.mExtras = p.readPersistableBundle(getClass().getClassLoader());
+                }
+                break;
         }
         eventOut.mFlags = p.readInt();
     }
@@ -1065,6 +1127,7 @@
     }
 
     private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+        dest.writeInt(mEventCount);
         dest.writeInt(mIndex);
         dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
     }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 4f1c993..85d223d 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -28,6 +28,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
@@ -35,6 +36,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Build;
+import android.os.PersistableBundle;
 import android.os.PowerWhitelistManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -392,6 +394,23 @@
     @SystemApi
     public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
 
+    /**
+     * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates
+     * the category of the user interaction associated with the event. The category cannot
+     * be more than 127 characters, longer value will be truncated to 127 characters.
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    public static final String EXTRA_EVENT_CATEGORY =
+            "android.app.usage.extra.EVENT_CATEGORY";
+
+    /**
+     * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates
+     * the action of the user interaction associated with the event. The action cannot be
+     * more than 127 characters, longer value will be truncated to 127 characters.
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    public static final String EXTRA_EVENT_ACTION =
+            "android.app.usage.extra.EVENT_ACTION";
 
     /**
      * App usage observers will consider the task root package the source of usage.
@@ -562,10 +581,10 @@
      * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
-     *                 Defined in terms of "Unix time", see
-     *                 {@link java.lang.System#currentTimeMillis}.
+     *                  Defined in terms of "Unix time", see
+     *                  {@link java.lang.System#currentTimeMillis}.
      * @param endTime The exclusive end of the range of events to include in the results. Defined
-     *               in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+     *                in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
      * @return A {@link UsageEvents}.
      */
     public UsageEvents queryEvents(long beginTime, long endTime) {
@@ -611,10 +630,10 @@
      * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
-     *                 Defined in terms of "Unix time", see
-     *                 {@link java.lang.System#currentTimeMillis}.
+     *                  Defined in terms of "Unix time", see
+     *                  {@link java.lang.System#currentTimeMillis}.
      * @param endTime The exclusive end of the range of events to include in the results. Defined
-     *               in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+     *                in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
      * @return A {@link UsageEvents} object.
      *
      * @see #queryEvents(long, long)
@@ -1128,6 +1147,7 @@
      * Reports user interaction with a given package in the given user.
      *
      * <p><em>This method is only for use by the system</em>
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)
@@ -1140,6 +1160,38 @@
     }
 
     /**
+     * Reports user interaction with given package and a particular {@code extras}
+     * in the given user.
+     *
+     * <p>
+     * Note: The structure of {@code extras} is a {@link PersistableBundle} with the
+     * category {@link #EXTRA_EVENT_CATEGORY} and the action {@link #EXTRA_EVENT_ACTION}.
+     * Category provides additional detail about the user interaction, the value
+     * is defined in namespace based. Example: android.app.notification could be used to
+     * indicate that the reported user interaction is related to notification. Action
+     * indicates the general action that performed.
+     * </p>
+     *
+     * @param packageName The package name of the app
+     * @param userId The user id who triggers the user interaction
+     * @param extras The {@link PersistableBundle} that will be used to specify the
+     *               extra details for the user interaction event. The {@link PersistableBundle}
+     *               must contain the extras {@link #EXTRA_EVENT_CATEGORY},
+     *               {@link #EXTRA_EVENT_ACTION}. Cannot be empty.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)
+    public void reportUserInteraction(@NonNull String packageName, @UserIdInt int userId,
+            @NonNull PersistableBundle extras) {
+        try {
+            mService.reportUserInteractionWithBundle(packageName, userId, extras);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Report usage associated with a particular {@code token} has started. Tokens are app defined
      * strings used to represent usage of in-app features. Apps with the {@link
      * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index c6012bb..51a7f1c 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -562,6 +563,40 @@
         });
     }
 
+    private void tryAdapterConversion(
+            FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action,
+            RemoteViews original, String failureMsg) {
+        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (original != null && original.hasLegacyLists()));
+
+        if (isConvertingAdapter) {
+            final RemoteViews viewsCopy = new RemoteViews(original);
+            Runnable updateWidgetWithTask = () -> {
+                try {
+                    viewsCopy.collectAllIntents().get();
+                    action.acceptOrThrow(viewsCopy);
+                } catch (Exception e) {
+                    Log.e(TAG, failureMsg, e);
+                }
+            };
+
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                createUpdateExecutorIfNull().execute(updateWidgetWithTask);
+                return;
+            }
+
+            updateWidgetWithTask.run();
+            return;
+        }
+
+        try {
+            action.acceptOrThrow(original);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      * <p>
@@ -586,32 +621,8 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.updateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error updating app widget views in background", e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds,
+                view), views, "Error updating app widget views in background");
     }
 
     /**
@@ -716,32 +727,9 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error partially updating app widget views in background", e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName,
+                appWidgetIds, view), views,
+                "Error partially updating app widget views in background");
     }
 
     /**
@@ -793,33 +781,8 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.updateAppWidgetProvider(provider, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error updating app widget view using provider in background",
-                                e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.updateAppWidgetProvider(provider, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views,
+                "Error updating app widget view using provider in background");
     }
 
     /**
diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java
deleted file mode 100644
index a382e09..0000000
--- a/core/java/android/companion/utils/FeatureUtils.java
+++ /dev/null
@@ -1,52 +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 android.companion.utils;
-
-import android.os.Binder;
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-/**
- * Util class for feature flags
- *
- * @hide
- */
-public final class FeatureUtils {
-
-    private static final String NAMESPACE_COMPANION = "companion";
-
-    private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled";
-
-    public static boolean isPermSyncEnabled() {
-        // Permissions sync is always enabled in debuggable mode.
-        if (Build.isDebuggable()) {
-            return true;
-        }
-
-        // Clear app identity to read the device config for feature flag.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return DeviceConfig.getBoolean(NAMESPACE_COMPANION,
-                    PROPERTY_PERM_SYNC_ENABLED, false);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private FeatureUtils() {
-    }
-}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 102cbf3..3520c0b 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -246,4 +246,10 @@
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterVirtualCamera(in VirtualCameraConfig camera);
+
+    /**
+     * Returns the id of the virtual camera with given config.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    int getVirtualCameraId(in VirtualCameraConfig camera);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index da8277c..9492a62 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.companion.virtual.audio.VirtualAudioDevice;
@@ -340,12 +341,17 @@
         return mVirtualAudioDevice;
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @NonNull
     VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
-        return new VirtualCamera(mVirtualDevice, config);
+        try {
+            mVirtualDevice.registerVirtualCamera(config);
+            return new VirtualCamera(mVirtualDevice, config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
-    @NonNull
     void setShowPointerIcon(boolean showPointerIcon) {
         try {
             mVirtualDevice.setShowPointerIcon(showPointerIcon);
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
index beee86f..52afa4e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -66,15 +66,8 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public VirtualCamera(
             @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
-        mVirtualDevice = virtualDevice;
+        mVirtualDevice = Objects.requireNonNull(virtualDevice);
         mConfig = Objects.requireNonNull(config);
-        Objects.requireNonNull(virtualDevice);
-        // TODO(b/310857519): Avoid registration inside constructor.
-        try {
-            mVirtualDevice.registerVirtualCamera(config);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
     }
 
     /** Returns the configuration of this virtual camera instance. */
@@ -83,6 +76,20 @@
         return mConfig;
     }
 
+    /**
+     * Returns the id of this virtual camera instance.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @NonNull
+    public String getId() {
+        try {
+            return Integer.toString(mVirtualDevice.getVirtualCameraId(mConfig));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 02066fa..f0477d4 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -1,3 +1,12 @@
+# Do not add new flags to this file.
+#
+# Due to "virtual" keyword in the package name flags
+# added to this file cannot be accessed from C++
+# code.
+#
+# Use frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig
+# instead.
+
 package: "android.companion.virtual.flags"
 
 flag {
@@ -23,6 +32,13 @@
 }
 
 flag {
+  name: "consistent_display_flags"
+  namespace: "virtual_devices"
+  description: "Make virtual display flags consistent with display manager"
+  bug: "300905478"
+}
+
+flag {
   name: "vdm_custom_home"
   namespace: "virtual_devices"
   description: "Enable custom home API"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
new file mode 100644
index 0000000..d26890f
--- /dev/null
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -0,0 +1,23 @@
+#
+# 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.companion.virtualdevice.flags"
+
+flag {
+     namespace: "virtual_devices"
+     name: "virtual_camera_service_discovery"
+     description: "Enable discovery of the Virtual Camera HAL without a VINTF entry"
+     bug: "305170199"
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1c917ee..1c6c7b5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -7560,7 +7560,7 @@
      * @throws UnsupportedOperationException if the method is called on an instance that is not
      *         associated with any display.
      */
-    @Nullable
+    @NonNull
     public Display getDisplay() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 665ba11..c7a86fb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12605,8 +12605,12 @@
         return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
     }
 
-    // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal}
-    /** @hide */
+    /**
+     * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivity} instead.
+     * Once the other API is finalized this method will be removed.
+     * @hide
+     */
+    @Deprecated
     @android.ravenwood.annotation.RavenwoodThrow
     public boolean isSandboxActivity(@NonNull Context context) {
         if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 59ed045..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.app.PendingIntent;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstallerCallback;
@@ -82,7 +83,7 @@
     void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
-    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+    void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
     void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
@@ -90,4 +91,6 @@
             in IntentSender statusReceiver,
             String installerPackageName, in UserHandle userHandle);
 
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+    void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index babfba1..98623de 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -312,6 +312,8 @@
 
     Bundle getSuspendedPackageAppExtras(String packageName, int userId);
 
+    String getSuspendingPackage(String packageName, int userId);
+
     /**
      * Backup/restore support - only the system uid may use these.
      */
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index a4d5327..cb3455b 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -22,11 +22,18 @@
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
+import android.icu.text.UnicodeSet;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
 /**
  * A representation of an activity that can belong to this user or a managed
  * profile associated with this user. It can be used to query the label, icon
@@ -36,6 +43,10 @@
     private final PackageManager mPm;
     private final LauncherActivityInfoInternal mInternal;
 
+    private static final UnicodeSet TRIMMABLE_CHARACTERS =
+            new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]",
+                    /* ignoreWhitespace= */ false).freeze();
+
     /**
      * Create a launchable activity object for a given ResolveInfo and user.
      *
@@ -77,8 +88,22 @@
      * @return The label for the activity.
      */
     public CharSequence getLabel() {
+        if (!Flags.lightweightInvisibleLabelDetection()) {
+            // TODO: Go through LauncherAppsService
+            return getActivityInfo().loadLabel(mPm);
+        }
+
+        CharSequence label = trim(getActivityInfo().loadLabel(mPm));
+        // If the trimmed label is empty, use application's label instead
+        if (TextUtils.isEmpty(label)) {
+            label = trim(getApplicationInfo().loadLabel(mPm));
+            // If the trimmed label is still empty, use package name instead
+            if (TextUtils.isEmpty(label)) {
+                label = getComponentName().getPackageName();
+            }
+        }
         // TODO: Go through LauncherAppsService
-        return getActivityInfo().loadLabel(mPm);
+        return label;
     }
 
     /**
@@ -180,4 +205,149 @@
 
         return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
     }
+
+    /**
+     * If the {@code ch} is trimmable, return {@code true}. Otherwise, return
+     * {@code false}. If the count of the code points of {@code ch} doesn't
+     * equal 1, return {@code false}.
+     * <p>
+     * There are two types of the trimmable characters.
+     * 1. The character is one of the Default_Ignorable_Code_Point in
+     * <a href="
+     * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt">
+     * DerivedCoreProperties.txt</a>, the White_Space in <a href=
+     * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt
+     * </a> or category Cc.
+     * <p>
+     * 2. The character is not supported in the current system font.
+     * {@link android.graphics.Paint#hasGlyph(String)}
+     * <p>
+     *
+     */
+    private static boolean isTrimmable(@NonNull Paint paint, @NonNull CharSequence ch) {
+        Objects.requireNonNull(paint);
+        Objects.requireNonNull(ch);
+
+        // if ch is empty or it is not a character (i,e, the count of code
+        // point doesn't equal one), return false
+        if (TextUtils.isEmpty(ch)
+                || Character.codePointCount(ch, /* beginIndex= */ 0, ch.length()) != 1) {
+            return false;
+        }
+
+        // Return true for the cases as below:
+        // 1. The character is in the TRIMMABLE_CHARACTERS set
+        // 2. The character is not supported in the system font
+        return TRIMMABLE_CHARACTERS.contains(ch) || !paint.hasGlyph(ch.toString());
+    }
+
+    /**
+     * If the {@code sequence} has some leading trimmable characters, creates a new copy
+     * and removes the trimmable characters from the copy. Otherwise the given
+     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
+     * to determine whether the character is trimmable or not.
+     *
+     * @return the trimmed string or the original string that has no
+     *         leading trimmable characters.
+     * @see    #isTrimmable(Paint, CharSequence)
+     * @see    #trim(CharSequence)
+     * @see    #trimEnd(CharSequence)
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    @NonNull
+    public static CharSequence trimStart(@NonNull CharSequence sequence) {
+        Objects.requireNonNull(sequence);
+
+        if (TextUtils.isEmpty(sequence)) {
+            return sequence;
+        }
+
+        final Paint paint = new Paint();
+        int trimCount = 0;
+        final int[] codePoints = sequence.codePoints().toArray();
+        for (int i = 0, length = codePoints.length; i < length; i++) {
+            String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1);
+            if (!isTrimmable(paint, ch)) {
+                break;
+            }
+            trimCount += ch.length();
+        }
+        if (trimCount == 0) {
+            return sequence;
+        }
+        return sequence.subSequence(trimCount, sequence.length());
+    }
+
+    /**
+     * If the {@code sequence} has some trailing trimmable characters, creates a new copy
+     * and removes the trimmable characters from the copy. Otherwise the given
+     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
+     * to determine whether the character is trimmable or not.
+     *
+     * @return the trimmed sequence or the original sequence that has no
+     *         trailing trimmable characters.
+     * @see    #isTrimmable(Paint, CharSequence)
+     * @see    #trimStart(CharSequence)
+     * @see    #trim(CharSequence)
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    @NonNull
+    public static CharSequence trimEnd(@NonNull CharSequence sequence) {
+        Objects.requireNonNull(sequence);
+
+        if (TextUtils.isEmpty(sequence)) {
+            return sequence;
+        }
+
+        final Paint paint = new Paint();
+        int trimCount = 0;
+        final int[] codePoints = sequence.codePoints().toArray();
+        for (int i = codePoints.length - 1; i >= 0; i--) {
+            String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1);
+            if (!isTrimmable(paint, ch)) {
+                break;
+            }
+            trimCount += ch.length();
+        }
+
+        if (trimCount == 0) {
+            return sequence;
+        }
+        return sequence.subSequence(0, sequence.length() - trimCount);
+    }
+
+    /**
+     * If the {@code sequence} has some leading or trailing trimmable characters, creates
+     * a new copy and removes the trimmable characters from the copy. Otherwise the given
+     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
+     * to determine whether the character is trimmable or not.
+     *
+     * @return the trimmed sequence or the original sequence that has no leading or
+     *         trailing trimmable characters.
+     * @see    #isTrimmable(Paint, CharSequence)
+     * @see    #trimStart(CharSequence)
+     * @see    #trimEnd(CharSequence)
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    @NonNull
+    public static CharSequence trim(@NonNull CharSequence sequence) {
+        Objects.requireNonNull(sequence);
+
+        if (TextUtils.isEmpty(sequence)) {
+            return sequence;
+        }
+
+        CharSequence result = trimStart(sequence);
+        if (TextUtils.isEmpty(result)) {
+            return result;
+        }
+
+        return trimEnd(result);
+    }
 }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 63c11b7..1cfdb8b 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,8 +16,11 @@
 
 package android.content.pm;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -244,6 +247,14 @@
     public Attribution[] attributions;
 
     /**
+     * The time at which the app was archived for the user.  Units are as
+     * per {@link System#currentTimeMillis()}.
+     * @hide
+     */
+    @CurrentTimeMillisLong
+    private long mArchiveTimeMillis;
+
+    /**
      * Flag for {@link #requestedPermissionsFlags}: the requested permission
      * is required for the application to run; the user can not optionally
      * disable it.  Currently all permissions are required.
@@ -508,6 +519,24 @@
         return overlayTarget != null && mOverlayIsStatic;
     }
 
+    /**
+     * Returns the time at which the app was archived for the user.  Units are as
+     * per {@link System#currentTimeMillis()}.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public @CurrentTimeMillisLong long getArchiveTimeMillis() {
+        return mArchiveTimeMillis;
+    }
+
+    /**
+     * @hide
+     */
+    public void setArchiveTimeMillis(@CurrentTimeMillisLong long value) {
+        mArchiveTimeMillis = value;
+    }
+
     @Override
     public String toString() {
         return "PackageInfo{"
@@ -575,6 +604,7 @@
         }
         dest.writeBoolean(isApex);
         dest.writeBoolean(isActiveApex);
+        dest.writeLong(mArchiveTimeMillis);
         dest.restoreAllowSquashing(prevAllowSquashing);
     }
 
@@ -640,5 +670,6 @@
         }
         isApex = source.readBoolean();
         isActiveApex = source.readBoolean();
+        mArchiveTimeMillis = source.readLong();
     }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 6681e54..6df1f60 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -363,6 +363,19 @@
             "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
 
     /**
+     * Extra field for the unarchive ID. Sent as
+     * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+     *
+     * @see Session#setUnarchiveId(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_UNARCHIVE_ID =
+            "android.content.pm.extra.UNARCHIVE_ID";
+
+    /**
      * If true, the requestor of the unarchival has specified that the app should be unarchived
      * for {@link android.os.UserHandle#ALL}.
      *
@@ -374,6 +387,24 @@
             "android.content.pm.extra.UNARCHIVE_ALL_USERS";
 
     /**
+     * Current status of an unarchive operation. Will be one of
+     * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED},
+     * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY},
+     * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or
+     * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}.
+     *
+     * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set
+     * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
+     * failure dialog.
+     *
+     * @see #requestUnarchive
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
+
+    /**
      * A list of warnings that occurred during installation.
      *
      * @hide
@@ -639,6 +670,102 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserActionReason {}
 
+    /**
+     * The unarchival is possible and will commence.
+     *
+     * <p> Note that this does not mean that the unarchival has completed. This status should be
+     * sent before any longer asynchronous action (e.g. app download) is started.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_OK = 0;
+
+    /**
+     * The user needs to interact with the installer to enable the installation.
+     *
+     * <p> An example use case for this could be that the user needs to login to allow the
+     * download for a paid app.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
+
+    /**
+     * Not enough storage to unarchive the application.
+     *
+     * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
+     * dialog. If no action is provided, then a generic intent
+     * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
+
+    /**
+     * The device is not connected to the internet
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
+
+    /**
+     * The installer responsible for the unarchival is disabled.
+     *
+     * <p> Should only be used by the system.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
+
+    /**
+     * The installer responsible for the unarchival has been uninstalled
+     *
+     * <p> Should only be used by the system.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
+
+    /**
+     * Generic error: The app cannot be unarchived.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final int UNARCHIVAL_GENERIC_ERROR = 100;
+
+    /**
+     * The set of error types that can be set for
+     * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+     *
+     * @hide
+     */
+    @IntDef(value = {
+            UNARCHIVAL_OK,
+            UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+            UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+            UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+            UNARCHIVAL_ERROR_INSTALLER_DISABLED,
+            UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
+            UNARCHIVAL_GENERIC_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UnarchivalStatus {}
+
+
     /** Default set of checksums - includes all available checksums.
      * @see Session#requestChecksums  */
     private static final int DEFAULT_CHECKSUMS =
@@ -2210,6 +2337,7 @@
      */
     @SystemApi
     @NonNull
+    @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO)
     public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd,
             @Nullable String debugPathName, int flags) throws PackageParsingException {
         final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
@@ -2225,8 +2353,8 @@
      * Requests to archive a package which is currently installed.
      *
      * <p> During the archival process, the apps APKs and cache are removed from the device while
-     * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
-     * restored again through their responsible installer.
+     * the user data is kept. Through the {@link #requestUnarchive} call, apps
+     * can be restored again through their responsible installer.
      *
      * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
      * will be displayed to users with UI treatment to highlight that said apps are archived. If
@@ -2265,9 +2393,15 @@
      * <p> The installation will happen asynchronously and can be observed through
      * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
      *
+     * @param statusReceiver Callback used to notify whether the installer has accepted the
+     *                       unarchival request or an error has occurred. The status update will be
+     *                       sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+     *                       sent.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              visible to the caller or if the package has no
      *                                              installer on the device anymore to unarchive it.
+     * @throws IOException If parameters were unsatisfiable, such as lack of disk space.
+     *
      * @hide
      */
     @RequiresPermission(anyOf = {
@@ -2275,11 +2409,45 @@
             Manifest.permission.REQUEST_INSTALL_PACKAGES})
     @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public void requestUnarchive(@NonNull String packageName)
+    public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+            throws IOException, PackageManager.NameNotFoundException {
+        try {
+            mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver,
+                    new UserHandle(mUserId));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Reports the status of an unarchival to the system.
+     *
+     * @param unarchiveId          the ID provided by the system as part of the
+     *                             intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID.
+     * @param status               is used for the system to provide the user with necessary
+     *                             follow-up steps or errors.
+     * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field
+     *                             should be set to specify how many additional bytes of storage
+     *                             are required to unarchive the app.
+     * @param userActionIntent     Optional intent to start a follow up action required to
+     *                             facilitate the unarchival flow (e.g. user needs to log in).
+     * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
+            long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
             throws PackageManager.NameNotFoundException {
         try {
-            mInstaller.requestUnarchive(packageName, mInstallerPackageName,
-                    new UserHandle(mUserId));
+            mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+                    userActionIntent, new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
         } catch (RemoteException e) {
@@ -2349,6 +2517,7 @@
          * and all relevant native code.
          * @throws IOException when size of native binaries cannot be calculated.
          */
+        @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO)
         public long calculateInstalledSize(@NonNull SessionParams params,
                 @NonNull ParcelFileDescriptor pfd) throws IOException {
             return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride,
@@ -2548,6 +2717,10 @@
         public boolean applicationEnabledSettingPersistent = false;
         /** {@hide} */
         public int developmentInstallFlags = 0;
+        /** {@hide} */
+        public int unarchiveId = -1;
+        /** {@hide} */
+        public IntentSender unarchiveIntentSender;
 
         private final ArrayMap<String, Integer> mPermissionStates;
 
@@ -2599,6 +2772,8 @@
             packageSource = source.readInt();
             applicationEnabledSettingPersistent = source.readBoolean();
             developmentInstallFlags = source.readInt();
+            unarchiveId = source.readInt();
+            unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
         }
 
         /** {@hide} */
@@ -2632,6 +2807,8 @@
             ret.packageSource = packageSource;
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
             ret.developmentInstallFlags = developmentInstallFlags;
+            ret.unarchiveId = unarchiveId;
+            ret.unarchiveIntentSender = unarchiveIntentSender;
             return ret;
         }
 
@@ -3270,6 +3447,22 @@
             }
         }
 
+        /**
+         * Used to set the unarchive ID received as part of an
+         * {@link Intent#ACTION_UNARCHIVE_PACKAGE}.
+         *
+         * <p> The ID should be retrieved from the unarchive intent and passed into the
+         * session that's being created to unarchive the app in question. Used to link the unarchive
+         * intent and the install session to disambiguate.
+         *
+         * @hide
+         */
+        @FlaggedApi(Flags.FLAG_ARCHIVING)
+        @SystemApi
+        public void setUnarchiveId(int unarchiveId) {
+            this.unarchiveId = unarchiveId;
+        }
+
         /** @hide */
         @NonNull
         public ArrayMap<String, Integer> getPermissionStates() {
@@ -3327,6 +3520,8 @@
             pw.printPair("applicationEnabledSettingPersistent",
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
+            pw.printPair("unarchiveId", unarchiveId);
+            pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
             pw.println();
         }
 
@@ -3370,6 +3565,8 @@
             dest.writeInt(packageSource);
             dest.writeBoolean(applicationEnabledSettingPersistent);
             dest.writeInt(developmentInstallFlags);
+            dest.writeInt(unarchiveId);
+            dest.writeParcelable(unarchiveIntentSender, flags);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 72e1066..6775f9b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -34,6 +34,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.annotation.XmlRes;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -43,6 +44,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.StorageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -95,6 +97,7 @@
 import dalvik.system.VMRuntime;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.cert.Certificate;
@@ -107,6 +110,7 @@
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Class for retrieving various kinds of information related to the application
@@ -710,12 +714,31 @@
      */
     @SystemApi
     public interface OnPermissionsChangedListener {
+        /**
+         * Called when the permissions for a UID change for the default device.
+         *
+         * @param uid The UID with a change.
+         * @see Context#DEVICE_ID_DEFAULT
+         */
+        void onPermissionsChanged(int uid);
 
         /**
-         * Called when the permissions for a UID change.
-         * @param uid The UID with a change.
+         * Called when the permissions for a UID change for a device, including virtual devices.
+         *
+         * @param uid The UID of permission change event.
+         * @param persistentDeviceId The persistent device ID of permission change event.
+         *
+         * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId()
+         * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT
          */
-        public void onPermissionsChanged(int uid);
+        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) {
+            Objects.requireNonNull(persistentDeviceId);
+            if (Objects.equals(persistentDeviceId,
+                    VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+                onPermissionsChanged(uid);
+            }
+        }
     }
 
     /** @hide */
@@ -1481,6 +1504,7 @@
             INSTALL_STAGED,
             INSTALL_REQUEST_UPDATE_OWNERSHIP,
             INSTALL_IGNORE_DEXOPT_PROFILE,
+            INSTALL_UNARCHIVE_DRAFT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallFlags {}
@@ -1725,6 +1749,16 @@
     public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28;
 
     /**
+     * If set, then the session is a draft session created for an upcoming unarchival by its
+     * installer.
+     *
+     * @see PackageInstaller#requestUnarchive(String)
+     *
+     * @hide
+     */
+    public static final int INSTALL_UNARCHIVE_DRAFT = 1 << 29;
+
+    /**
      * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
      * a development-only feature and should not be used on end user devices.
      *
@@ -4019,6 +4053,15 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports multiple concurrent IME sessions.
+     */
+    @FlaggedApi("android.view.inputmethod.concurrent_input_methods")
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CONCURRENT_INPUT_METHODS =
+            "android.software.concurrent_input_methods";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports device policy enforcement via device admins.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -6364,9 +6407,8 @@
     /**
      * Permission flags set when granting or revoking a permission.
      *
-     * @hide
+     * @removed mistakenly exposed as system-api previously
      */
-    @SystemApi
     @IntDef(prefix = { "FLAG_PERMISSION_" }, value = {
             FLAG_PERMISSION_USER_SET,
             FLAG_PERMISSION_USER_FIXED,
@@ -9781,7 +9823,8 @@
      * launcher to support customization that they might need to handle the suspended state.
      *
      * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API except for
-     * device owner and profile owner.
+     * device owner and profile owner or the {@link Manifest.permission#QUARANTINE_APPS} if the
+     * caller is using {@link #FLAG_SUSPEND_QUARANTINED}.
      *
      * @param packageNames The names of the packages to set the suspended status.
      * @param suspended If set to {@code true}, the packages will be suspended, if set to
@@ -9809,7 +9852,10 @@
      */
     @SystemApi
     @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
-    @RequiresPermission(value=Manifest.permission.SUSPEND_APPS, conditional=true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.SUSPEND_APPS,
+            Manifest.permission.QUARANTINE_APPS
+    }, conditional = true)
     @SuppressLint("NullableCollection")
     @Nullable
     public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
@@ -9919,6 +9965,21 @@
     }
 
     /**
+     * Get the name of the package that suspended the given package. Packages can be suspended by
+     * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
+     * {@link android.Manifest.permission#SUSPEND_APPS}.
+     *
+     * @param suspendedPackage The package that has been suspended.
+     * @return Name of the package that suspended the given package. Returns {@code null} if the
+     * given package is not currently suspended and the platform package name - i.e.
+     * {@code "android"} - if the package was suspended by a device admin.
+     * @hide
+     */
+    public @Nullable String getSuspendingPackage(@NonNull String suspendedPackage) {
+        throw new UnsupportedOperationException("getSuspendingPackage not implemented");
+    }
+
+    /**
      * Query if an app is currently stopped.
      *
      * @return {@code true} if the given package is stopped, {@code false} otherwise
@@ -11383,4 +11444,60 @@
         throw new UnsupportedOperationException(
                 "unregisterPackageMonitorCallback not implemented in subclass");
     }
+
+    /**
+     * Retrieve AndroidManifest.xml information for the given application apk path.
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * Bundle result;
+     * try {
+     *     result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+     *             xmlResourceParser -> {
+     *                 Bundle bundle = new Bundle();
+     *                 // Search the start tag
+     *                 int type;
+     *                 while ((type = xmlResourceParser.next()) != XmlPullParser.START_TAG
+     *                         &amp;&amp; type != XmlPullParser.END_DOCUMENT) {
+     *                 }
+     *                 if (type != XmlPullParser.START_TAG) {
+     *                     return bundle;
+     *                 }
+     *
+     *                 // Start to read the tags and attributes from the xmlResourceParser
+     *                 if (!xmlResourceParser.getName().equals("manifest")) {
+     *                     return bundle;
+     *                 }
+     *                 String packageName = xmlResourceParser.getAttributeValue(null, "package");
+     *                 bundle.putString("package", packageName);
+     *
+     *                 // Continue to read the tags and attributes from the xmlResourceParser
+     *
+     *                 return bundle;
+     *             });
+     * } catch (IOException e) {
+     * }
+     * </code></pre>
+     *
+     * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
+     * information by the XmlResourceParser object. After leaving the parserFunction, the
+     * XmlResourceParser object will be closed.
+     *
+     * @param apkFilePath The path of an application apk file.
+     * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
+     *        after getting the AndroidManifest.xml of an application package.
+     *
+     * @return Returns the result of the {@link Function#apply(Object)}.
+     *
+     * @throws IOException if the AndroidManifest.xml of an application package cannot be
+     *             read or accessed.
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
+    @WorkerThread
+    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+            @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+        throw new UnsupportedOperationException(
+                "parseAndroidManifest not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index f532c4c..445ca0c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -49,7 +50,8 @@
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
     private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
-    private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode";
+    private static final String ATTR_SHOW_IN_QUIET_MODE = "showInQuietMode";
+    private static final String ATTR_SHOW_IN_SHARING_SURFACES = "showInSharingSurfaces";
     private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
     private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
     private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA =
@@ -81,7 +83,8 @@
             INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT,
             INDEX_DELETE_APP_WITH_PARENT,
             INDEX_ALWAYS_VISIBLE,
-            INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+            INDEX_SHOW_IN_QUIET_MODE,
+            INDEX_SHOW_IN_SHARING_SURFACES,
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -99,8 +102,9 @@
     private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9;
     private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
     private static final int INDEX_ALWAYS_VISIBLE = 11;
-    private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
+    private static final int INDEX_SHOW_IN_QUIET_MODE = 12;
     private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
+    private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -286,6 +290,81 @@
      */
     public static final int CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING = 1;
 
+    /**
+     * Possible values for the profile visibility when in quiet mode. This affects the profile data
+     * and apps surfacing in Settings, sharing surfaces, and file picker surfaces. It signifies
+     * whether the profile data and apps will be shown or not.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SHOW_IN_QUIET_MODE_",
+            value = {
+                    SHOW_IN_QUIET_MODE_PAUSED,
+                    SHOW_IN_QUIET_MODE_HIDDEN,
+                    SHOW_IN_QUIET_MODE_DEFAULT,
+            }
+    )
+    public @interface ShowInQuietMode {
+    }
+
+    /**
+     * Indicates that the profile should still be visible in quiet mode but should be shown as
+     * paused (e.g. by greying out its icons).
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_QUIET_MODE_PAUSED = 0;
+    /**
+     * Indicates that the profile should not be visible when the profile is in quiet mode.
+     * For example, the profile should not be shown in tabbed views in Settings, files sharing
+     * surfaces etc when in quiet mode.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1;
+    /**
+     * Indicates that quiet mode should not have any effect on the profile visibility. If the
+     * profile is meant to be visible, it will remain visible and vice versa.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2;
+
+    /**
+     * Possible values for the profile apps visibility in sharing surfaces. This indicates the
+     * profile data and apps should be shown in separate tabs or mixed with its parent user's data
+     * and apps in sharing surfaces and file picker surfaces.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_",
+            value = {
+                    SHOW_IN_SHARING_SURFACES_SEPARATE,
+                    SHOW_IN_SHARING_SURFACES_WITH_PARENT,
+                    SHOW_IN_SHARING_SURFACES_NO,
+            }
+    )
+    public @interface ShowInSharingSurfaces {
+    }
+
+    /**
+     * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with
+     * parent user's data and apps.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = SHOW_IN_LAUNCHER_WITH_PARENT;
+
+    /**
+     * Indicates that the profile data and apps should be shown in sharing surfaces separate from
+     * parent user's data and apps.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = SHOW_IN_LAUNCHER_SEPARATE;
+
+    /**
+     * Indicates that the profile data and apps should not be shown in sharing surfaces at all.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SHARING_SURFACES_NO = SHOW_IN_LAUNCHER_NO;
 
     /**
      * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
@@ -331,7 +410,6 @@
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
             setShowInSettings(orig.getShowInSettings());
-            setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
             setUseParentsContacts(orig.getUseParentsContacts());
             setAuthAlwaysRequiredToDisableQuietMode(
                     orig.isAuthAlwaysRequiredToDisableQuietMode());
@@ -343,6 +421,8 @@
         setShowInLauncher(orig.getShowInLauncher());
         setMediaSharedWithParent(orig.isMediaSharedWithParent());
         setCredentialShareableWithParent(orig.isCredentialShareableWithParent());
+        setShowInQuietMode(orig.getShowInQuietMode());
+        setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
     }
 
     /**
@@ -419,40 +499,59 @@
     private @ShowInSettings int mShowInSettings;
 
     /**
-     * Returns whether a user should be shown in the Settings app depending on the quiet mode.
-     * This is generally inapplicable for non-profile users.
+     * Returns whether a user should be shown in the Settings and sharing surfaces depending on the
+     * {@link android.os.UserManager#requestQuietModeEnabled(boolean, android.os.UserHandle)
+     * quiet mode}. This is only applicable to profile users since the quiet mode concept is only
+     * applicable to profile users.
      *
-     * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings.
-     * However, if this behaviour should be changed based on the quiet mode of the user, then this
-     * property can be used. If the property is not set then the user is shown in the Settings app
-     * irrespective of whether the user is in quiet mode or not. If the property is set, then the
-     * user is shown in the Settings app only if the user is not in the quiet mode. Please note that
-     * this property takes effect only if {@link #getShowInSettings()} does not return
-     * {@link #SHOW_IN_SETTINGS_NO}.
+     * <p> Please note that, in Settings, this property takes effect only if
+     * {@link #getShowInSettings()} does not return {@link #SHOW_IN_SETTINGS_NO}.
+     * Also note that in Sharing surfaces this property takes effect only if
+     * {@link #getShowInSharingSurfaces()} does not return {@link #SHOW_IN_SHARING_SURFACES_NO}.
      *
-     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
-     * property.
-     *
-     * @return true if a profile should be shown in the Settings only when the user is not in the
-     * quiet mode.
-     *
-     * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)},
-     * {@link ShowInSettings}
-     *
-     * @hide
+     * @return One of {@link #SHOW_IN_QUIET_MODE_HIDDEN},
+     *         {@link #SHOW_IN_QUIET_MODE_PAUSED}, or
+     *         {@link #SHOW_IN_QUIET_MODE_DEFAULT} depending on whether the profile should be
+     *         shown in quiet mode or not.
      */
-    public boolean getHideInSettingsInQuietMode() {
-        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode;
-        if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode;
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public @ShowInQuietMode int getShowInQuietMode() {
+        // NOTE: Launcher currently does not make use of this property.
+        if (isPresent(INDEX_SHOW_IN_QUIET_MODE)) return mShowInQuietMode;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInQuietMode;
         throw new SecurityException(
-                "You don't have permission to query HideInSettingsInQuietMode");
+                "You don't have permission to query ShowInQuietMode");
     }
     /** @hide */
-    public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
-        this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
-        setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE);
+    public void setShowInQuietMode(@ShowInQuietMode int showInQuietMode) {
+        this.mShowInQuietMode = showInQuietMode;
+        setPresent(INDEX_SHOW_IN_QUIET_MODE);
     }
-    private boolean mHideInSettingsInQuietMode;
+    private int mShowInQuietMode;
+
+    /**
+     * Returns whether a user's data and apps should be shown in sharing surfaces in a separate tab
+     * or mixed with the parent user's data/apps. This is only applicable to profile users.
+     *
+     * @return One of {@link #SHOW_IN_SHARING_SURFACES_NO},
+     *         {@link #SHOW_IN_SHARING_SURFACES_SEPARATE}, or
+     *         {@link #SHOW_IN_SHARING_SURFACES_WITH_PARENT} depending on whether the profile
+     *         should be shown separate from its parent's data, mixed with the parent's data, or
+     *         not shown at all.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public @ShowInSharingSurfaces int getShowInSharingSurfaces() {
+        if (isPresent(INDEX_SHOW_IN_SHARING_SURFACES)) return mShowInSharingSurfaces;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInSharingSurfaces;
+        throw new SecurityException(
+                "You don't have permission to query ShowInSharingSurfaces");
+    }
+    /** @hide */
+    public void setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) {
+        this.mShowInSharingSurfaces = showInSharingSurfaces;
+        setPresent(INDEX_SHOW_IN_SHARING_SURFACES);
+    }
+    private int mShowInSharingSurfaces;
 
     /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
@@ -799,8 +898,11 @@
                 case ATTR_SHOW_IN_SETTINGS:
                     setShowInSettings(parser.getAttributeInt(i));
                     break;
-                case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE:
-                    setHideInSettingsInQuietMode(parser.getAttributeBoolean(i));
+                case ATTR_SHOW_IN_QUIET_MODE:
+                    setShowInQuietMode(parser.getAttributeInt(i));
+                    break;
+                case ATTR_SHOW_IN_SHARING_SURFACES:
+                    setShowInSharingSurfaces(parser.getAttributeInt(i));
                     break;
                 case ATTR_INHERIT_DEVICE_POLICY:
                     setInheritDevicePolicy(parser.getAttributeInt(i));
@@ -858,9 +960,12 @@
         if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
             serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
         }
-        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) {
-            serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE,
-                    mHideInSettingsInQuietMode);
+        if (isPresent(INDEX_SHOW_IN_QUIET_MODE)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_QUIET_MODE,
+                    mShowInQuietMode);
+        }
+        if (isPresent(INDEX_SHOW_IN_SHARING_SURFACES)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_SHARING_SURFACES, mShowInSharingSurfaces);
         }
         if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) {
             serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
@@ -912,7 +1017,8 @@
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
         dest.writeInt(mShowInSettings);
-        dest.writeBoolean(mHideInSettingsInQuietMode);
+        dest.writeInt(mShowInQuietMode);
+        dest.writeInt(mShowInSharingSurfaces);
         dest.writeInt(mInheritDevicePolicy);
         dest.writeBoolean(mUseParentsContacts);
         dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
@@ -936,7 +1042,8 @@
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
         mShowInSettings = source.readInt();
-        mHideInSettingsInQuietMode = source.readBoolean();
+        mShowInQuietMode = source.readInt();
+        mShowInSharingSurfaces = source.readInt();
         mInheritDevicePolicy = source.readInt();
         mUseParentsContacts = source.readBoolean();
         mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
@@ -974,7 +1081,10 @@
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
         private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
-        private boolean mHideInSettingsInQuietMode = false;
+        private @ShowInQuietMode int mShowInQuietMode =
+                SHOW_IN_QUIET_MODE_PAUSED;
+        private @ShowInSharingSurfaces int mShowInSharingSurfaces =
+                SHOW_IN_SHARING_SURFACES_SEPARATE;
         private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
         private boolean mUseParentsContacts = false;
         private boolean mUpdateCrossProfileIntentFiltersOnOTA = false;
@@ -1005,9 +1115,15 @@
             return this;
         }
 
-        /** Sets the value for {@link #mHideInSettingsInQuietMode} */
-        public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
-            mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
+        /** Sets the value for {@link #mShowInQuietMode} */
+        public Builder setShowInQuietMode(@ShowInQuietMode int showInQuietMode) {
+            mShowInQuietMode = showInQuietMode;
+            return this;
+        }
+
+        /** Sets the value for {@link #mShowInSharingSurfaces}. */
+        public Builder setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) {
+            mShowInSharingSurfaces = showInSharingSurfaces;
             return this;
         }
 
@@ -1081,7 +1197,8 @@
                     mShowInLauncher,
                     mStartWithParent,
                     mShowInSettings,
-                    mHideInSettingsInQuietMode,
+                    mShowInQuietMode,
+                    mShowInSharingSurfaces,
                     mInheritDevicePolicy,
                     mUseParentsContacts,
                     mUpdateCrossProfileIntentFiltersOnOTA,
@@ -1100,7 +1217,8 @@
             @ShowInLauncher int showInLauncher,
             boolean startWithParent,
             @ShowInSettings int showInSettings,
-            boolean hideInSettingsInQuietMode,
+            @ShowInQuietMode int showInQuietMode,
+            @ShowInSharingSurfaces int showInSharingSurfaces,
             @InheritDevicePolicy int inheritDevicePolicy,
             boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
             @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
@@ -1114,7 +1232,8 @@
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
         setShowInSettings(showInSettings);
-        setHideInSettingsInQuietMode(hideInSettingsInQuietMode);
+        setShowInQuietMode(showInQuietMode);
+        setShowInSharingSurfaces(showInSharingSurfaces);
         setInheritDevicePolicy(inheritDevicePolicy);
         setUseParentsContacts(useParentsContacts);
         setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 814eae6..a565f68 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -80,3 +80,17 @@
     description: "Feature flag to retrieve resolved path of the base APK during an app install."
     bug: "269728874"
 }
+
+flag {
+    name: "lightweight_invisible_label_detection"
+    namespace: "package_manager_service"
+    description: "Feature flag to detect the invisible labels in Launcher Apps"
+    bug: "299586370"
+}
+
+flag {
+    name: "read_install_info"
+    namespace: "package_manager_service"
+    description: "Feature flag to read install related information from an APK."
+    bug: "275658500"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9ec082a..6c6b33b 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -42,3 +42,10 @@
     description: "Allow using all cpu cores during a user switch."
     bug: "308105403"
 }
+
+flag {
+    name: "enable_biometrics_to_unlock_private_space"
+    namespace: "profile_experiences"
+    description: "Add support to unlock the private space using biometrics"
+    bug: "312184187"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index f3194be..4990a27 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -140,6 +140,11 @@
     private final boolean mIsSdkLibrary;
 
     /**
+     * Indicates if this system app can be updated.
+     */
+    private final boolean mUpdatableSystem;
+
+    /**
      * Archival install info.
      */
     private final @Nullable ArchivedPackageParcel mArchivedPackage;
@@ -154,7 +159,7 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) {
         mPath = path;
         mPackageName = packageName;
         mSplitName = splitName;
@@ -188,6 +193,7 @@
         mRollbackDataPolicy = rollbackDataPolicy;
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
+        mUpdatableSystem = updatableSystem;
         mArchivedPackage = null;
     }
 
@@ -225,6 +231,7 @@
         mRollbackDataPolicy = 0;
         mHasDeviceAdminReceiver = false;
         mIsSdkLibrary = false;
+        mUpdatableSystem = true;
         mArchivedPackage = archivedPackage;
     }
 
@@ -535,6 +542,14 @@
     }
 
     /**
+     * Indicates if this system app can be updated.
+     */
+    @DataClass.Generated.Member
+    public boolean isUpdatableSystem() {
+        return mUpdatableSystem;
+    }
+
+    /**
      * Archival install info.
      */
     @DataClass.Generated.Member
@@ -543,10 +558,10 @@
     }
 
     @DataClass.Generated(
-            time = 1694792109463L,
+            time = 1699587291575L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5f86742..4626679 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -424,6 +424,7 @@
                 0);
         int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
         boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+        boolean updatableSystem = parser.getAttributeBooleanValue(null, "updatableSystem", true);
         boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
                 "isolatedSplits", false);
         boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
@@ -505,14 +506,18 @@
                         continue;
                     }
 
-                    if (TAG_PROFILEABLE.equals(parser.getName())) {
-                        profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
-                                "shell", profilableByShell);
-                    } else if (TAG_RECEIVER.equals(parser.getName())) {
-                        hasDeviceAdminReceiver |= isDeviceAdminReceiver(
-                                parser, hasBindDeviceAdminPermission);
-                    } else if (TAG_SDK_LIBRARY.equals(parser.getName())) {
-                        isSdkLibrary = true;
+                    switch (parser.getName()) {
+                        case TAG_PROFILEABLE:
+                            profilableByShell = parser.getAttributeBooleanValue(
+                                    ANDROID_RES_NAMESPACE, "shell", profilableByShell);
+                            break;
+                        case TAG_RECEIVER:
+                            hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser,
+                                    hasBindDeviceAdminPermission);
+                            break;
+                        case TAG_SDK_LIBRARY:
+                            isSdkLibrary = true;
+                            break;
                     }
                 }
             } else if (TAG_OVERLAY.equals(parser.getName())) {
@@ -614,7 +619,7 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary));
+                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 1667896..38dbec5 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -38,9 +38,11 @@
     private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
     private static final int MAX_ATTR_LEN_PACKAGE = 256;
     private static final int MAX_ATTR_LEN_MIMETYPE = 512;
-    public static final int MAX_ATTR_LEN_NAME = 1024;
-    public static final int MAX_ATTR_LEN_PATH = 4000;
-    public static final int MAX_ATTR_LEN_VALUE = 32_768;
+    private static final int MAX_ATTR_LEN_NAME = 1024;
+    private static final int MAX_ATTR_LEN_PATH = 4000;
+    private static final int MAX_ATTR_LEN_VALUE = 32_768;
+
+    private static final int MAX_TOTAL_META_DATA_SIZE = 262_144;
 
     private static final String BAD_COMPONENT_NAME_CHARS = ";,[](){}:?%^*|/\\";
 
@@ -157,6 +159,7 @@
     }
 
     private long mChildTagMask = 0;
+    private int mTotalComponentMetadataSize = 0;
 
     private static int getCounterIdx(String tag) {
         switch(tag) {
@@ -283,6 +286,7 @@
     private void init(String tag) {
         this.mTag = tag;
         mChildTagMask = 0;
+        mTotalComponentMetadataSize = 0;
         switch (tag) {
             case TAG_ACTIVITY:
                 initializeCounter(TAG_LAYOUT, 1000);
@@ -820,6 +824,12 @@
         }
     }
 
+    void validateComponentMetadata(String value) {
+        mTotalComponentMetadataSize += value.length();
+        if (mTotalComponentMetadataSize > MAX_TOTAL_META_DATA_SIZE) {
+            throw new SecurityException("Max total meta data size limit exceeded for " + mTag);
+        }
+    }
 
     void seen(@NonNull Element element) {
         TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java
index cae353b..3b68452 100644
--- a/core/java/android/content/res/Validator.java
+++ b/core/java/android/content/res/Validator.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.StyleableRes;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -84,6 +86,9 @@
             return;
         }
         mElements.peek().validateResStrAttr(index, stringValue);
+        if (index == R.styleable.AndroidManifestMetaData_value) {
+            validateComponentMetadata(stringValue.toString());
+        }
     }
 
     /**
@@ -94,5 +99,20 @@
             return;
         }
         mElements.peek().validateStrAttr(attrName, attrValue);
+        if (attrName.equals(Element.TAG_ATTR_VALUE)) {
+            validateComponentMetadata(attrValue);
+        }
+    }
+
+    private void validateComponentMetadata(String attrValue) {
+        Element element = mElements.peek();
+        // Meta-data values are evaluated on the parent element which is the next element in the
+        // mElements stack after the meta-data element. The top of the stack is always the current
+        // element being validated so check that the top element is meta-data.
+        if (element.mTag.equals(Element.TAG_META_DATA) && mElements.size() > 1) {
+            element = mElements.pop();
+            mElements.peek().validateComponentMetadata(attrValue);
+            mElements.push(element);
+        }
     }
 }
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 69d573f..80f085f 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -33,11 +33,11 @@
 import java.util.Map;
 import java.util.Objects;
 
-
 /**
  * This is an abstract cursor class that handles a lot of the common code
  * that all cursors need to deal with and is provided for convenience reasons.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class AbstractCursor implements CrossProcessCursor {
     private static final String TAG = "Cursor";
 
@@ -89,7 +89,7 @@
     private Bundle mExtras = Bundle.EMPTY;
 
     /** CloseGuard to detect leaked cursor **/
-    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final CloseGuard mCloseGuard;
 
     /* -------------------------------------------------------- */
     /* These need to be implemented by subclasses */
@@ -184,7 +184,9 @@
         mClosed = true;
         mContentObservable.unregisterAll();
         onDeactivateOrClose();
-        mCloseGuard.close();
+        if (mCloseGuard != null) {
+            mCloseGuard.close();
+        }
     }
 
     /**
@@ -224,7 +226,19 @@
     /* Implementation */
     public AbstractCursor() {
         mPos = -1;
-        mCloseGuard.open("AbstractCursor.close");
+        mCloseGuard = initCloseGuard();
+        if (mCloseGuard != null) {
+            mCloseGuard.open("AbstractCursor.close");
+        }
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private CloseGuard initCloseGuard() {
+        return CloseGuard.get();
+    }
+
+    private CloseGuard initCloseGuard$ravenwood() {
+        return null;
     }
 
     @Override
diff --git a/core/java/android/database/CharArrayBuffer.java b/core/java/android/database/CharArrayBuffer.java
index 73781b7..4927654 100644
--- a/core/java/android/database/CharArrayBuffer.java
+++ b/core/java/android/database/CharArrayBuffer.java
@@ -19,6 +19,7 @@
 /**
  * This is used for {@link Cursor#copyStringToBuffer}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CharArrayBuffer {
     public CharArrayBuffer(int size) {
         data = new char[size];
diff --git a/core/java/android/database/ContentObservable.java b/core/java/android/database/ContentObservable.java
index 7692bb3..dc35b5a 100644
--- a/core/java/android/database/ContentObservable.java
+++ b/core/java/android/database/ContentObservable.java
@@ -23,6 +23,7 @@
  * that provides methods for sending notifications to a list of
  * {@link ContentObserver} objects.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ContentObservable extends Observable<ContentObserver> {
     // Even though the generic method defined in Observable would be perfectly
     // fine on its own, we can't delete this overridden method because it would
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index c27ee51..39c9400 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -36,6 +36,7 @@
  * Receives call backs for changes to content.
  * Must be implemented by objects which are added to a {@link ContentObservable}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class ContentObserver {
     /**
      * Starting in {@link android.os.Build.VERSION_CODES#R}, there is a new
@@ -49,7 +50,6 @@
     @ChangeId
     @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
     private static final long ADD_CONTENT_OBSERVER_FLAGS = 150939131L;
-
     private final Object mLock = new Object();
     private Transport mTransport; // guarded by mLock
 
@@ -216,7 +216,7 @@
         // There are dozens of people relying on the hidden API inside the
         // system UID, so hard-code the old behavior for all of them; for
         // everyone else we gate based on a specific change
-        if (!CompatChanges.isChangeEnabled(ADD_CONTENT_OBSERVER_FLAGS)
+        if (!isChangeEnabledAddContentObserverFlags()
                 || android.os.Process.myUid() == android.os.Process.SYSTEM_UID) {
             // Deliver userId through argument to preserve hidden API behavior
             onChange(selfChange, uris, flags, UserHandle.of(userId));
@@ -225,6 +225,15 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean isChangeEnabledAddContentObserverFlags() {
+        return CompatChanges.isChangeEnabled(ADD_CONTENT_OBSERVER_FLAGS);
+    }
+
+    private static boolean isChangeEnabledAddContentObserverFlags$ravenwood() {
+        return true;
+    }
+
     /**
      * Dispatches a change notification to the observer.
      * <p>
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index afa1c20..cb1d3f5 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -40,6 +40,7 @@
  * Implementations should subclass {@link AbstractCursor}.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface Cursor extends Closeable {
     /*
      * Values returned by {@link #getType(int)}.
diff --git a/core/java/android/database/CursorIndexOutOfBoundsException.java b/core/java/android/database/CursorIndexOutOfBoundsException.java
index 1f77d00..89d4418 100644
--- a/core/java/android/database/CursorIndexOutOfBoundsException.java
+++ b/core/java/android/database/CursorIndexOutOfBoundsException.java
@@ -19,6 +19,7 @@
 /**
  * An exception indicating that a cursor is out of bounds.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CursorIndexOutOfBoundsException extends IndexOutOfBoundsException {
 
     public CursorIndexOutOfBoundsException(int index, int size) {
diff --git a/core/java/android/database/CursorJoiner.java b/core/java/android/database/CursorJoiner.java
index a95263b..2eb81a1 100644
--- a/core/java/android/database/CursorJoiner.java
+++ b/core/java/android/database/CursorJoiner.java
@@ -42,6 +42,7 @@
  * }
  * </pre>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CursorJoiner
         implements Iterator<CursorJoiner.Result>, Iterable<CursorJoiner.Result> {
     private Cursor mCursorLeft;
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 4496f80..6572f99 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -27,6 +27,7 @@
  * Wrapper class for Cursor that delegates all calls to the actual cursor object.  The primary
  * use for this class is to extend a cursor while overriding only a subset of its methods.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CursorWrapper implements Cursor {
     /** @hide */
     @UnsupportedAppUsage
diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java
index ca77a13..ff47f9a 100644
--- a/core/java/android/database/DataSetObservable.java
+++ b/core/java/android/database/DataSetObservable.java
@@ -21,6 +21,7 @@
  * that provides methods for sending notifications to a list of
  * {@link DataSetObserver} objects.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DataSetObservable extends Observable<DataSetObserver> {
     /**
      * Invokes {@link DataSetObserver#onChanged} on each observer.
diff --git a/core/java/android/database/DataSetObserver.java b/core/java/android/database/DataSetObserver.java
index 28616c8..13469cb 100644
--- a/core/java/android/database/DataSetObserver.java
+++ b/core/java/android/database/DataSetObserver.java
@@ -21,6 +21,7 @@
  * that are observed are {@link Cursor}s or {@link android.widget.Adapter}s.
  * DataSetObserver must be implemented by objects which are added to a DataSetObservable.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class DataSetObserver {
     /**
      * This method is called when the entire data set has changed,
diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java
index 050a49a..ad35b2f 100644
--- a/core/java/android/database/MatrixCursor.java
+++ b/core/java/android/database/MatrixCursor.java
@@ -26,6 +26,7 @@
  * {@link #newRow()} to add rows. Automatically expands internal capacity
  * as needed.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MatrixCursor extends AbstractCursor {
 
     private final String[] columnNames;
diff --git a/core/java/android/database/MergeCursor.java b/core/java/android/database/MergeCursor.java
index 272cfa2..5a56756 100644
--- a/core/java/android/database/MergeCursor.java
+++ b/core/java/android/database/MergeCursor.java
@@ -22,6 +22,7 @@
  * may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
  * value for the row that the MergeCursor is currently pointing at.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MergeCursor extends AbstractCursor
 {
     private DataSetObserver mObserver = new DataSetObserver() {
diff --git a/core/java/android/database/Observable.java b/core/java/android/database/Observable.java
index aff32db..a3057ac 100644
--- a/core/java/android/database/Observable.java
+++ b/core/java/android/database/Observable.java
@@ -26,6 +26,7 @@
  *
  * @param T The observer type.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class Observable<T> {
     /**
      * The list of observers.  An observer can be in the list at most
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 89f889f..fe95a2a 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1414,7 +1414,7 @@
      * otherwise the value will be equal to 1.
      * Note that this level is just a number of supported levels (the granularity of control).
      * There is no actual physical power units tied to this level.</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#FLASH_MODE
      */
@@ -1430,7 +1430,7 @@
      * or equal to <code>android.flash.info.singleStrengthMaxLevel</code>.
      * Note for devices that do not support the manual flash strength control
      * feature, this level will always be equal to 1.</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      */
     @PublicKey
     @NonNull
@@ -1450,7 +1450,7 @@
      * android.flash.info.singleStrengthMaxLevel i.e. the ratio of
      * android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel
      * is not guaranteed to be the ratio of actual brightness.</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#FLASH_MODE
      */
@@ -1466,7 +1466,7 @@
      * or equal to android.flash.info.torchStrengthMaxLevel.
      * Note for the devices that do not support the manual flash strength control feature,
      * this level will always be equal to 1.</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index a978bd8..0a61c32 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -736,6 +736,9 @@
                     return generateJpegSupportedSizes(
                             extenders.second.getSupportedPostviewResolutions(sz),
                                     streamMap);
+                }  else if (format == ImageFormat.JPEG_R) {
+                    // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                    return new ArrayList<>();
                 } else {
                     throw new IllegalArgumentException("Unsupported format: " + format);
                 }
@@ -891,6 +894,9 @@
                         } else {
                             return generateSupportedSizes(null, format, streamMap);
                         }
+                    } else if (format == ImageFormat.JPEG_R) {
+                        // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                        return new ArrayList<>();
                     } else {
                         throw new IllegalArgumentException("Unsupported format: " + format);
                     }
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index dfc27ca..507e814 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -24,6 +25,8 @@
 import android.hardware.camera2.impl.SyntheticKey;
 import android.util.Log;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5d06978..93cae54 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2688,7 +2688,7 @@
      * set to TORCH;
      * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to SINGLE</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#FLASH_MODE
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 0d204f3..12ab0f6 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2974,7 +2974,7 @@
      * set to TORCH;
      * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to SINGLE</p>
-     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#FLASH_MODE
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 7756b9c..c379188 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -22,9 +22,11 @@
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
@@ -45,6 +47,8 @@
 @VisibleForTesting(visibility = Visibility.PACKAGE)
 public final class DeviceStateManagerGlobal {
     private static DeviceStateManagerGlobal sInstance;
+    private static final String TAG = "DeviceStateManagerGlobal";
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     /**
      * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
@@ -400,11 +404,29 @@
         }
 
         void notifyBaseStateChanged(int newBaseState) {
-            mExecutor.execute(() -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
+            execute("notifyBaseStateChanged",
+                    () -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
         }
 
         void notifyStateChanged(int newDeviceState) {
-            mExecutor.execute(() -> mDeviceStateCallback.onStateChanged(newDeviceState));
+            execute("notifyStateChanged",
+                    () -> mDeviceStateCallback.onStateChanged(newDeviceState));
+        }
+
+        private void execute(String traceName, Runnable r) {
+            mExecutor.execute(() -> {
+                if (DEBUG) {
+                    Trace.beginSection(
+                            mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName);
+                }
+                try {
+                    r.run();
+                } finally {
+                    if (DEBUG) {
+                        Trace.endSection();
+                    }
+                }
+            });
         }
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 4791a83..f71e853 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -721,8 +721,19 @@
     public interface DisplayOffloadSession {
         /** Provide the display state to use in place of state DOZE. */
         void setDozeStateOverride(int displayState);
-        /** Returns the associated DisplayOffloader. */
-        DisplayOffloader getDisplayOffloader();
+
+        /** Whether the session is active. */
+        boolean isActive();
+
+        /**
+         * Update the brightness from the offload chip.
+         * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
+         *                   {@link PowerManager.BRIGHTNESS_MAX}, or
+         *                   {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} which removes
+         *                   the brightness from offload. Other values will be ignored.
+         */
+        void updateBrightness(float brightness);
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 440585c..09741e52 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -149,6 +149,7 @@
     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
 
+    /** @removed mistakenly exposed previously */
     @IntDef ({
         RESULT_SUCCESS,
         RESULT_TIMEOUT,
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 7f2d8a0..5985c39 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -22,6 +22,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import java.util.Arrays;
@@ -80,7 +81,10 @@
                                 + event.getKeyCode()
                                 + " sent to a VirtualDpad input device.");
             }
-            mVirtualDevice.sendDpadKeyEvent(mToken, event);
+            if (!mVirtualDevice.sendDpadKeyEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send key event to virtual dpad "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java
index 931e1ff..affa4ed 100644
--- a/core/java/android/hardware/input/VirtualInputDevice.java
+++ b/core/java/android/hardware/input/VirtualInputDevice.java
@@ -20,6 +20,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.io.Closeable;
 
@@ -32,6 +33,8 @@
  */
 abstract class VirtualInputDevice implements Closeable {
 
+    protected static final String TAG = "VirtualInputDevice";
+
     /**
      * The virtual device to which this VirtualInputDevice belongs to.
      */
@@ -67,6 +70,7 @@
     @Override
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
+        Log.d(TAG, "Closing virtual input device " + mConfig.getInputDeviceName());
         try {
             mVirtualDevice.unregisterInputDevice(mToken);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
index a8caa58..a87980c 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -19,6 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
+import android.view.Display;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
 
 /**
  * Common configurations to create virtual input devices.
@@ -27,6 +31,15 @@
  */
 @SystemApi
 public abstract class VirtualInputDeviceConfig {
+
+    /**
+     * The maximum length of a device name (in bytes in UTF-8 encoding).
+     *
+     * This limitation comes directly from uinput.
+     * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h
+     */
+    private static final int DEVICE_NAME_MAX_LENGTH = 80;
+
     /** The vendor id uniquely identifies the company who manufactured the device. */
     private final int mVendorId;
     /**
@@ -44,18 +57,33 @@
         mVendorId = builder.mVendorId;
         mProductId = builder.mProductId;
         mAssociatedDisplayId = builder.mAssociatedDisplayId;
-        mInputDeviceName = builder.mInputDeviceName;
+        mInputDeviceName = Objects.requireNonNull(builder.mInputDeviceName);
+
+        if (mAssociatedDisplayId == Display.INVALID_DISPLAY) {
+            throw new IllegalArgumentException(
+                    "Display association is required for virtual input devices.");
+        }
+
+        // Comparison is greater or equal because the device name must fit into a const char*
+        // including the \0-terminator. Therefore the actual number of bytes that can be used
+        // for device name is DEVICE_NAME_MAX_LENGTH - 1
+        if (mInputDeviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) {
+            throw new IllegalArgumentException("Input device name exceeds maximum length of "
+                    + DEVICE_NAME_MAX_LENGTH + "bytes: " + mInputDeviceName);
+        }
     }
 
     protected VirtualInputDeviceConfig(@NonNull Parcel in) {
         mVendorId = in.readInt();
         mProductId = in.readInt();
         mAssociatedDisplayId = in.readInt();
-        mInputDeviceName = in.readString8();
+        mInputDeviceName = Objects.requireNonNull(in.readString8());
     }
 
     /**
      * The vendor id uniquely identifies the company who manufactured the device.
+     *
+     * @see Builder#setVendorId(int) (int)
      */
     public int getVendorId() {
         return mVendorId;
@@ -64,6 +92,8 @@
     /**
      * The product id uniquely identifies which product within the address space of a given vendor,
      * identified by the device's vendor id.
+     *
+     * @see Builder#setProductId(int)
      */
     public int getProductId() {
         return mProductId;
@@ -71,6 +101,8 @@
 
     /**
      * The associated display ID of the virtual input device.
+     *
+     * @see Builder#setAssociatedDisplayId(int)
      */
     public int getAssociatedDisplayId() {
         return mAssociatedDisplayId;
@@ -78,6 +110,8 @@
 
     /**
      * The name of the virtual input device.
+     *
+     * @see Builder#setInputDeviceName(String)
      */
     @NonNull
     public String getInputDeviceName() {
@@ -117,11 +151,12 @@
 
         private int mVendorId;
         private int mProductId;
-        private int mAssociatedDisplayId;
-        @NonNull
+        private int mAssociatedDisplayId = Display.INVALID_DISPLAY;
         private String mInputDeviceName;
 
-        /** @see VirtualInputDeviceConfig#getVendorId(). */
+        /**
+         * Sets the vendor id of the device, identifying the company who manufactured the device.
+         */
         @NonNull
         public T setVendorId(int vendorId) {
             mVendorId = vendorId;
@@ -129,24 +164,40 @@
         }
 
 
-        /** @see VirtualInputDeviceConfig#getProductId(). */
+        /**
+         * Sets the product id of the device, uniquely identifying the device within the address
+         * space of a given vendor, identified by the device's vendor id.
+         */
         @NonNull
         public T setProductId(int productId) {
             mProductId = productId;
             return self();
         }
 
-        /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */
+        /**
+         * Sets the associated display ID of the virtual input device. Required.
+         *
+         * <p>The input device is restricted to the display with the given ID and may not send
+         * events to any other display.</p>
+         */
         @NonNull
         public T setAssociatedDisplayId(int displayId) {
             mAssociatedDisplayId = displayId;
             return self();
         }
 
-        /** @see VirtualInputDeviceConfig#getInputDeviceName(). */
+        /**
+         * Sets the name of the virtual input device. Required.
+         *
+         * <p>The name must be unique among all input devices that belong to the same virtual
+         * device.</p>
+         *
+         * <p>The maximum allowed length of the name is 80 bytes in UTF-8 encoding, enforced by
+         * {@code UINPUT_MAX_NAME_SIZE}.</p>
+         */
         @NonNull
         public T setInputDeviceName(@NonNull String deviceName) {
-            mInputDeviceName = deviceName;
+            mInputDeviceName = Objects.requireNonNull(deviceName);
             return self();
         }
 
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index c90f893..6eb2ae3 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -22,6 +22,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.KeyEvent;
 
 /**
@@ -57,7 +58,10 @@
                     "Unsupported key code " + event.getKeyCode()
                         + " sent to a VirtualKeyboard input device.");
             }
-            mVirtualDevice.sendKeyEvent(mToken, event);
+            if (!mVirtualDevice.sendKeyEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send key event to virtual keyboard "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
index 51f3f69..fb0f700 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -23,6 +23,7 @@
 import android.graphics.PointF;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.MotionEvent;
 
 /**
@@ -52,7 +53,10 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) {
         try {
-            mVirtualDevice.sendButtonEvent(mToken, event);
+            if (!mVirtualDevice.sendButtonEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send button event to virtual mouse "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -69,7 +73,10 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) {
         try {
-            mVirtualDevice.sendScrollEvent(mToken, event);
+            if (!mVirtualDevice.sendScrollEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send scroll event to virtual mouse "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -85,7 +92,10 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) {
         try {
-            mVirtualDevice.sendRelativeEvent(mToken, event);
+            if (!mVirtualDevice.sendRelativeEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send relative event to virtual mouse "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
index 61d72e2..3dbb385 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -22,6 +22,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * A virtual navigation touchpad representing a touch-based input mechanism on a remote device.
@@ -53,7 +54,10 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
         try {
-            mVirtualDevice.sendTouchEvent(mToken, event);
+            if (!mVirtualDevice.sendTouchEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send touch event to virtual navigation touchpad "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
index 4ac439e..2c800aa 100644
--- a/core/java/android/hardware/input/VirtualTouchscreen.java
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -22,6 +22,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * A virtual touchscreen representing a touch-based display input mechanism on a remote device.
@@ -47,7 +48,10 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
         try {
-            mVirtualDevice.sendTouchEvent(mToken, event);
+            if (!mVirtualDevice.sendTouchEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send touch event to virtual touchscreen "
+                        + mConfig.getInputDeviceName());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index bb3e81a..7ac1dd1 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -56,6 +56,8 @@
      *
      * @param targetNanoAppId the ID of the nanoapp to send the message to
      * @param messageType the nanoapp-dependent message type
+     *                    the value CHRE_MESSAGE_TYPE_RPC (0x7FFFFFF5) is reserved by the
+     *                    framework for RPC messages
      * @param messageBody the byte array message contents
      *
      * @return the NanoAppMessage object
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index c7ec052..7e5c141 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -109,7 +109,10 @@
     /** @deprecated use {@link ProgramIdentifier} instead */
     @Deprecated
     public static final int PROGRAM_TYPE_VENDOR_END = 1999;
-    /** @deprecated use {@link ProgramIdentifier} instead */
+    /**
+     * @deprecated use {@link ProgramIdentifier} instead
+     * @removed mistakenly exposed previously
+     */
     @Deprecated
     @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
         PROGRAM_TYPE_INVALID,
@@ -397,6 +400,7 @@
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
+    /** @removed mistakenly exposed previously */
     @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
         IDENTIFIER_TYPE_INVALID,
         IDENTIFIER_TYPE_AMFM_FREQUENCY,
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 237ec01..f0f7e8a 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -123,6 +123,7 @@
     /** AM HD radio or DRM band.
      * @see BandDescriptor */
     public static final int BAND_AM_HD = 3;
+    /** @removed mistakenly exposed previously */
     @IntDef(prefix = { "BAND_" }, value = {
         BAND_INVALID,
         BAND_AM,
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index d959240..4a5c4c8 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -646,12 +646,7 @@
      * @return array including {@link #COMPLIANCE_WARNING_OTHER},
      *         {@link #COMPLIANCE_WARNING_DEBUG_ACCESSORY},
      *         {@link #COMPLIANCE_WARNING_BC_1_2},
-     *         {@link #COMPLIANCE_WARNING_MISSING_RP},
-     *         {@link #COMPLIANCE_WARNING_INPUT_POWER_LIMITED},
-     *         {@link #COMPLIANCE_WARNING_MISSING_DATA_LINES},
-     *         {@link #COMPLIANCE_WARNING_ENUMERATION_FAIL},
-     *         {@link #COMPLIANCE_WARNING_FLAKY_CONNECTION},
-     *         {@link #COMPLIANCE_WARNING_UNRELIABLE_IO}.
+     *         {@link #COMPLIANCE_WARNING_MISSING_RP}.
      */
     @CheckResult
     @NonNull
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index f16e243..e2d215e 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -33,6 +33,8 @@
 import android.view.inputmethod.InputMethodSession;
 import android.window.WindowProviderService;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -72,8 +74,9 @@
      *         {@code null} if {@link #onCreateInputMethodInterface()} is not yet called.
      * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Nullable
-    protected final InputMethod getInputMethodInternal() {
+    public final InputMethod getInputMethodInternal() {
         return mInputMethod;
     }
 
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ba80811..18d3e5e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -149,6 +149,7 @@
 import android.window.WindowMetricsHelper;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethod;
@@ -3997,6 +3998,16 @@
     }
 
     /**
+     * Returns whether the IME navigation bar is currently shown, for testing purposes.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public final boolean isImeNavigationBarShownForTesting() {
+        return mNavigationBarController.isShown();
+    }
+
+    /**
      * Used to inject custom {@link InputMethodServiceInternal}.
      *
      * @return the {@link InputMethodServiceInternal} to be used.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 8be4c58..9c55b0e 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -77,6 +77,10 @@
         default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
         }
 
+        default boolean isShown() {
+            return false;
+        }
+
         default String toDebugString() {
             return "No-op implementation";
         }
@@ -117,6 +121,13 @@
         mImpl.onNavButtonFlagsChanged(navButtonFlags);
     }
 
+    /**
+     * Returns whether the IME navigation bar is currently shown.
+     */
+    boolean isShown() {
+        return mImpl.isShown();
+    }
+
     String toDebugString() {
         return mImpl.toDebugString();
     }
@@ -561,6 +572,12 @@
         }
 
         @Override
+        public boolean isShown() {
+            return mNavigationBarFrame != null
+                    && mNavigationBarFrame.getVisibility() == View.VISIBLE;
+        }
+
+        @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
                     + " mNavigationBarFrame=" + mNavigationBarFrame
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 19ba6a1..dbb3127 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -23,7 +23,6 @@
 import android.os.IBinder;
 import android.os.ServiceManager;
 
-import com.android.net.flags.Flags;
 import com.android.net.module.util.PermissionUtils;
 /**
  * Constants and utilities for client code communicating with the network stack service.
@@ -104,16 +103,4 @@
             final @NonNull String... otherPermissions) {
         PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions);
     }
-
-    /**
-     * Get setting of the "set_data_saver_via_cm" flag.
-     *
-     * @hide
-     */
-    // A workaround for aconfig. Currently, aconfig value read from platform and mainline code can
-    // be inconsistent. To avoid the problem, CTS for mainline code can get the flag value by this
-    // method.
-    public static boolean getDataSaverViaCmFlag() {
-        return Flags.setDataSaverViaCm();
-    }
 }
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 597c948..e331c95 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,6 +21,7 @@
 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;
@@ -134,8 +135,9 @@
     /**
      * @hide
      */
+    @UnsupportedAppUsage
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, int bannerResource, int uid,
             String settingsActivityName, String offHost, String staticOffHost) {
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
@@ -147,7 +149,7 @@
      * @hide
      */
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, int bannerResource, int uid,
             String settingsActivityName, String offHost, String staticOffHost,
             boolean isEnabled) {
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index ca84b35..6a83cee 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -682,6 +682,7 @@
 
     static class BatteryConsumerDataLayout {
         private static final Key[] KEY_ARRAY = new Key[0];
+        public static final int POWER_MODEL_NOT_INCLUDED = -1;
         public final String[] customPowerComponentNames;
         public final int customPowerComponentCount;
         public final boolean powerModelsIncluded;
@@ -713,7 +714,9 @@
                 // Declare the Key for the power component, ignoring other dimensions.
                 perComponentKeys.add(
                         new Key(componentId, PROCESS_STATE_ANY,
-                                powerModelsIncluded ? columnIndex++ : -1,  // power model
+                                powerModelsIncluded
+                                        ? columnIndex++
+                                        : POWER_MODEL_NOT_INCLUDED,  // power model
                                 columnIndex++,      // power
                                 columnIndex++       // usage duration
                         ));
@@ -736,7 +739,9 @@
 
                             perComponentKeys.add(
                                     new Key(componentId, processState,
-                                            powerModelsIncluded ? columnIndex++ : -1, // power model
+                                            powerModelsIncluded
+                                                    ? columnIndex++
+                                                    : POWER_MODEL_NOT_INCLUDED, // power model
                                             columnIndex++,      // power
                                             columnIndex++       // usage duration
                                     ));
@@ -843,11 +848,27 @@
 
         @SuppressWarnings("unchecked")
         @NonNull
+        public T addConsumedPower(@PowerComponent int componentId, double componentPower,
+                @PowerModel int powerModel) {
+            mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+                    componentPower, powerModel);
+            return (T) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        @NonNull
         public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
             mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel);
             return (T) this;
         }
 
+        @SuppressWarnings("unchecked")
+        @NonNull
+        public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
+            mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel);
+            return (T) this;
+        }
+
         /**
          * Sets the amount of drain attributed to the specified custom drain type.
          *
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e85b7bf..16ffaef 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -4029,6 +4029,17 @@
     }
 
     /**
+     * A helper object passed to various dump... methods to integrate with such objects
+     * as BatteryUsageStatsProvider.
+     */
+    public interface BatteryStatsDumpHelper {
+        /**
+         * Generates BatteryUsageStats based on the specified BatteryStats.
+         */
+        BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed);
+    }
+
+    /**
      * Dumps the ControllerActivityCounter if it has any data worth dumping.
      * The order of the arguments in the final check in line is:
      *
@@ -4390,7 +4401,7 @@
      * NOTE: all times are expressed in microseconds, unless specified otherwise.
      */
     public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
-            boolean wifiOnly) {
+            boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) {
 
         if (which != BatteryStats.STATS_SINCE_CHARGED) {
             dumpLine(pw, 0, STAT_NAMES[which], "err",
@@ -4652,7 +4663,7 @@
             }
         }
 
-        final BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */);
+        final BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */);
         dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
                 formatCharge(stats.getBatteryCapacity()),
                 formatCharge(stats.getConsumedPower()),
@@ -5127,7 +5138,7 @@
 
     @SuppressWarnings("unused")
     public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which,
-            int reqUid, boolean wifiOnly) {
+            int reqUid, boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) {
 
         if (which != BatteryStats.STATS_SINCE_CHARGED) {
             pw.println("ERROR: BatteryStats.dump called for which type " + which
@@ -5854,7 +5865,7 @@
         pw.println();
 
 
-        BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */);
+        BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */);
         stats.dump(pw, prefix);
 
         List<UidMobileRadioStats> uidMobileRadioStats =
@@ -7642,10 +7653,11 @@
     /**
      * Dumps a human-readable summary of the battery statistics to the given PrintWriter.
      *
-     * @param pw a Printer to receive the dump output.
+     * @param pw         a Printer to receive the dump output.
      */
     @SuppressWarnings("unused")
-    public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
+    public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart,
+            BatteryStatsDumpHelper dumpHelper) {
         synchronized (this) {
             prepareForDumpLocked();
         }
@@ -7663,12 +7675,12 @@
         }
 
         synchronized (this) {
-            dumpLocked(context, pw, flags, reqUid, filtering);
+            dumpLocked(context, pw, flags, reqUid, filtering, dumpHelper);
         }
     }
 
     private void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid,
-            boolean filtering) {
+            boolean filtering, BatteryStatsDumpHelper dumpHelper) {
         if (!filtering) {
             SparseArray<? extends Uid> uidStats = getUidStats();
             final int NU = uidStats.size();
@@ -7803,15 +7815,15 @@
             pw.println("  System starts: " + getStartCount()
                     + ", currently on battery: " + getIsOnBattery());
             dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid,
-                    (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper);
             pw.println();
         }
     }
 
     // This is called from BatteryStatsService.
     @SuppressWarnings("unused")
-    public void dumpCheckin(Context context, PrintWriter pw,
-            List<ApplicationInfo> apps, int flags, long histStart) {
+    public void dumpCheckin(Context context, PrintWriter pw, List<ApplicationInfo> apps, int flags,
+            long histStart, BatteryStatsDumpHelper dumpHelper) {
         synchronized (this) {
             prepareForDumpLocked();
 
@@ -7829,12 +7841,12 @@
         }
 
         synchronized (this) {
-            dumpCheckinLocked(context, pw, apps, flags);
+            dumpCheckinLocked(context, pw, apps, flags, dumpHelper);
         }
     }
 
     private void dumpCheckinLocked(Context context, PrintWriter pw, List<ApplicationInfo> apps,
-            int flags) {
+            int flags, BatteryStatsDumpHelper dumpHelper) {
         if (apps != null) {
             SparseArray<Pair<ArrayList<String>, MutableBoolean>> uids = new SparseArray<>();
             for (int i=0; i<apps.size(); i++) {
@@ -7881,7 +7893,7 @@
                         (Object[])lineArgs);
             }
             dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1,
-                    (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper);
         }
     }
 
@@ -7891,7 +7903,7 @@
      * @hide
      */
     public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
-            int flags, long histStart) {
+            int flags, long histStart, BatteryStatsDumpHelper dumpHelper) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
         prepareForDumpLocked();
 
@@ -7909,7 +7921,8 @@
         proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
 
         if ((flags & DUMP_DAILY_ONLY) == 0) {
-            final BatteryUsageStats stats = getBatteryUsageStats(context, false /* detailed */);
+            final BatteryUsageStats stats =
+                    dumpHelper.getBatteryUsageStats(this, false /* detailed */);
             ProportionalAttributionCalculator proportionalAttributionCalculator =
                     new ProportionalAttributionCalculator(context, stats);
             dumpProtoAppsLocked(proto, stats, apps, proportionalAttributionCalculator);
@@ -8856,8 +8869,6 @@
         return !tm.isDataCapable();
     }
 
-    protected abstract BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed);
-
     private boolean shouldHidePowerComponent(int powerComponent) {
         return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE
                 || powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index a5f8844..ed31002 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -36,6 +36,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -115,6 +116,7 @@
     static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package";
     static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground";
     static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background";
+    static final String XML_ATTR_TIME_IN_FOREGROUND_SERVICE = "time_in_foreground_service";
 
     // We need about 700 bytes per UID
     private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700;
@@ -585,7 +587,8 @@
                             + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
                 }
                 printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
-                        deviceConsumer.getPowerModel(key),
+                        mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
+                                : BatteryConsumer.POWER_MODEL_UNDEFINED,
                         deviceConsumer.getUsageDurationMillis(key));
             }
         }
@@ -773,6 +776,15 @@
         super.finalize();
     }
 
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        dump(pw, "");
+        pw.flush();
+        return sw.toString();
+    }
+
     /**
      * Builder for BatteryUsageStats.
      */
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index f817fb8..1100731 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -82,8 +82,8 @@
         private static final int MAIN_INDEX_SIZE = 1 <<  LOG_MAIN_INDEX_SIZE;
         private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
         /**
-         * Debuggable builds will throw an BinderProxyMapSizeException if the number of
-         * map entries exceeds:
+         * We will throw a BinderProxyMapSizeException if the number of map entries
+         * exceeds:
          */
         private static final int CRASH_AT_SIZE = 25_000;
 
diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java
index 65ed618..61aa222 100644
--- a/core/java/android/os/DeadObjectException.java
+++ b/core/java/android/os/DeadObjectException.java
@@ -19,8 +19,29 @@
 
 /**
  * The object you are calling has died, because its hosting process
- * no longer exists. This is also thrown for low-level binder
- * errors.
+ * no longer exists, or there has been a low-level binder error.
+ *
+ * If you get this exception from a system service, the error is
+ * usually nonrecoverable as the framework will restart. If you
+ * receive this error from an app, at a minimum, you should
+ * recover by resetting the connection. For instance, you should
+ * drop the binder, clean up associated state, and reset your
+ * connection to the service which through this error. In order
+ * to simplify your error recovery paths, you may also want to
+ * "simply" restart your process. However, this may not be an
+ * option if the service you are talking to is unreliable or
+ * crashes frequently.
+ *
+ * If this isn't from a service death and is instead from a
+ * low-level binder error, it will be from:
+ * - a oneway call queue filling up (too many oneway calls)
+ * - from the binder buffer being filled up, so that the transaction
+ *   is rejected.
+ *
+ * In these cases, more information about the error will be
+ * logged. However, there isn't a good way to differentiate
+ * this information at runtime. So, you should handle the
+ * error, as if the service died.
  */
 public class DeadObjectException extends RemoteException {
     public DeadObjectException() {
diff --git a/core/java/android/os/DeadSystemRuntimeException.java b/core/java/android/os/DeadSystemRuntimeException.java
index 82b1ad8..3b10798 100644
--- a/core/java/android/os/DeadSystemRuntimeException.java
+++ b/core/java/android/os/DeadSystemRuntimeException.java
@@ -19,10 +19,12 @@
 /**
  * Exception thrown when a call into system_server resulted in a
  * DeadObjectException, meaning that the system_server has died or
- * experienced a low-level binder error.  There's * nothing apps can
+ * experienced a low-level binder error.  There's nothing apps can
  * do at this point - the system will automatically restart - so
  * there's no point in catching this.
  *
+ * See {@link android.os.DeadObjectException}.
+ *
  * @hide
  */
 public class DeadSystemRuntimeException extends RuntimeException {
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 22d6fcd..92b630f 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -63,6 +63,7 @@
  * your new thread.  The given Runnable or Message will then be scheduled
  * in the Handler's message queue and processed when appropriate.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Handler {
     /*
      * Set this flag to true to detect anonymous, local or member classes
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 4dd797a..fcd5731 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -25,6 +25,7 @@
  * <p>
  * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class HandlerThread extends Thread {
     int mPriority;
     int mTid = -1;
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index fe85da2..6b43e73 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -17,8 +17,6 @@
 
 package android.os;
 
-import android.os.WorkDuration;
-
 /** {@hide} */
 oneway interface IHintSession {
     void updateTargetWorkDuration(long targetDurationNanos);
@@ -26,5 +24,4 @@
     void close();
     void sendHint(int hint);
     void setMode(int mode, boolean enabled);
-    void reportActualWorkDuration2(in WorkDuration[] workDurations);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 330b992..c0d1fb9 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -131,6 +131,7 @@
     int getUserBadgeDarkColorResId(int userId);
     int getUserStatusBarIconResId(int userId);
     boolean hasBadge(int userId);
+    int getProfileLabelResId(int userId);
     boolean isUserUnlocked(int userId);
     boolean isUserRunning(int userId);
     boolean isUserForeground(int userId);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 712d328..ddf2b61 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -24,6 +24,8 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import java.util.Objects;
+
 /**
   * Class used to run a message loop for a thread.  Threads by default do
   * not have a message loop associated with them; to create one, call
@@ -54,6 +56,7 @@
   *      }
   *  }</pre>
   */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Looper {
     /*
      * API Implementation Note:
@@ -144,6 +147,30 @@
     }
 
     /**
+     * Force the application's main looper to the given value.  The main looper is typically
+     * configured automatically by the OS, so this capability is only intended to enable testing.
+     *
+     * @hide
+     */
+    public static void setMainLooperForTest(@NonNull Looper looper) {
+        synchronized (Looper.class) {
+            sMainLooper = Objects.requireNonNull(looper);
+        }
+    }
+
+    /**
+     * Clear the application's main looper to be undefined.  The main looper is typically
+     * configured automatically by the OS, so this capability is only intended to enable testing.
+     *
+     * @hide
+     */
+    public static void clearMainLooperForTest() {
+        synchronized (Looper.class) {
+            sMainLooper = null;
+        }
+    }
+
+    /**
      * Set the transaction observer for all Loopers in this process.
      *
      * @hide
@@ -282,11 +309,7 @@
 
         // Allow overriding a threshold with a system prop. e.g.
         // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
-        final int thresholdOverride =
-                SystemProperties.getInt("log.looper."
-                        + Process.myUid() + "."
-                        + Thread.currentThread().getName()
-                        + ".slow", -1);
+        final int thresholdOverride = getThresholdOverride();
 
         me.mSlowDeliveryDetected = false;
 
@@ -297,6 +320,18 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static int getThresholdOverride() {
+        return SystemProperties.getInt("log.looper."
+                + Process.myUid() + "."
+                + Thread.currentThread().getName()
+                + ".slow", -1);
+    }
+
+    private static int getThresholdOverride$ravenwood() {
+        return -1;
+    }
+
     private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
             String what, Message msg) {
         final long actualTime = measureEnd - measureStart;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 72fb4ae..da647e2 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -33,6 +33,7 @@
  * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
  * them from a pool of recycled objects.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Message implements Parcelable {
     /**
      * User-defined message code so that the recipient can identify
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 9d8a71b..c60f949 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -40,6 +40,9 @@
  * <p>You can retrieve the MessageQueue for the current thread with
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.MessageQueue_host")
 public final class MessageQueue {
     private static final String TAG = "MessageQueue";
     private static final boolean DEBUG = false;
@@ -194,6 +197,7 @@
      * @see OnFileDescriptorEventListener
      * @see #removeOnFileDescriptorEventListener
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
             @OnFileDescriptorEventListener.Events int events,
             @NonNull OnFileDescriptorEventListener listener) {
@@ -221,6 +225,7 @@
      * @see OnFileDescriptorEventListener
      * @see #addOnFileDescriptorEventListener
      */
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
@@ -231,6 +236,7 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
             OnFileDescriptorEventListener listener) {
         final int fdNum = fd.getInt$();
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 8f7725ec..655debc 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -87,3 +87,7 @@
 
 # PerformanceHintManager
 per-file PerformanceHintManager.java = file:/ADPF_OWNERS
+
+# IThermal interfaces
+per-file IThermal* = file:/THERMAL_OWNERS
+
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e005910..11084b8 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -103,7 +103,7 @@
      * Any call in this class will change its internal data, so you must do your own thread
      * safety to protect from racing.
      *
-     * All timings should be in {@link SystemClock#uptimeNanos()}.
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
      */
     public static class Session implements Closeable {
         private long mNativeSessionPtr;
@@ -269,40 +269,6 @@
         public @Nullable int[] getThreadIds() {
             return nativeGetThreadIds(mNativeSessionPtr);
         }
-
-        /**
-         * Reports the work duration for the last cycle of work.
-         *
-         * The system will attempt to adjust the core placement of the threads within the thread
-         * group and/or the frequency of the core on which they are run to bring the actual duration
-         * close to the target duration.
-         *
-         * @param workDuration the work duration of each component.
-         * @throws IllegalArgumentException if work period start timestamp is not positive, or
-         *         actual total duration is not positive, or actual CPU duration is not positive,
-         *         or actual GPU duration is negative.
-         */
-        @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
-        public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
-            if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
-                throw new IllegalArgumentException(
-                    "the work period start timestamp should be positive.");
-            }
-            if (workDuration.mActualTotalDurationNanos <= 0) {
-                throw new IllegalArgumentException("the actual total duration should be positive.");
-            }
-            if (workDuration.mActualCpuDurationNanos <= 0) {
-                throw new IllegalArgumentException("the actual CPU duration should be positive.");
-            }
-            if (workDuration.mActualGpuDurationNanos < 0) {
-                throw new IllegalArgumentException(
-                    "the actual GPU duration should be non negative.");
-            }
-            nativeReportActualWorkDuration(mNativeSessionPtr,
-                    workDuration.mWorkPeriodStartTimestampNanos,
-                    workDuration.mActualTotalDurationNanos,
-                    workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos);
-        }
     }
 
     private static native long nativeAcquireManager();
@@ -319,7 +285,4 @@
     private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
     private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
             boolean enabled);
-    private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
-            long workPeriodStartTimestampNanos, long actualTotalDurationNanos,
-            long actualCpuDurationNanos, long actualGpuDurationNanos);
 }
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 9e5f539..9c11ad4 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
@@ -118,7 +119,7 @@
 
     @BatteryConsumer.PowerModel
     int getPowerModel(BatteryConsumer.Key key) {
-        if (key.mPowerModelColumnIndex == -1) {
+        if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
             throw new IllegalStateException(
                     "Power model IDs were not requested in the BatteryUsageStatsQuery");
         }
@@ -468,7 +469,7 @@
             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
                 for (BatteryConsumer.Key key : keys) {
-                    if (key.mPowerModelColumnIndex != -1) {
+                    if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
                         mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
                     }
                 }
@@ -478,11 +479,19 @@
         @NonNull
         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
                 int powerModel) {
-            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
-                componentPower = 0;
-            }
             mData.putDouble(key.mPowerColumnIndex, componentPower);
-            if (key.mPowerModelColumnIndex != -1) {
+            if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
+                mData.putInt(key.mPowerModelColumnIndex, powerModel);
+            }
+            return this;
+        }
+
+        @NonNull
+        public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower,
+                int powerModel) {
+            mData.putDouble(key.mPowerColumnIndex,
+                    mData.getDouble(key.mPowerColumnIndex) + componentPower);
+            if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
                 mData.putInt(key.mPowerModelColumnIndex, powerModel);
             }
             return this;
@@ -496,9 +505,6 @@
          */
         @NonNull
         public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
-            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
-                componentPower = 0;
-            }
             final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
             if (index < 0 || index >= mData.layout.customPowerComponentCount) {
                 throw new IllegalArgumentException(
@@ -575,12 +581,12 @@
                             mData.getLong(key.mDurationColumnIndex)
                                     + otherData.getLong(otherKey.mDurationColumnIndex));
 
-                    if (key.mPowerModelColumnIndex == -1) {
+                    if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
                         continue;
                     }
 
                     boolean undefined = false;
-                    if (otherKey.mPowerModelColumnIndex == -1) {
+                    if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
                         undefined = true;
                     } else {
                         final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
@@ -641,19 +647,26 @@
          */
         @NonNull
         public PowerComponents build() {
-            mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
-
             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
                 for (BatteryConsumer.Key key : keys) {
-                    if (key.mPowerModelColumnIndex != -1) {
+                    if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
                         if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
                             mData.putInt(key.mPowerModelColumnIndex,
                                     BatteryConsumer.POWER_MODEL_UNDEFINED);
                         }
                     }
+
+                    if (mMinConsumedPowerThreshold != 0) {
+                        if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
+                            mData.putDouble(key.mPowerColumnIndex, 0);
+                        }
+                    }
                 }
             }
 
+            if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) {
+                mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
+            }
             return new PowerComponents(this);
         }
     }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 13572fb..11660f9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -842,15 +842,19 @@
         return "amd64".equals(System.getProperty("os.arch"));
     }
 
-    private static SomeArgs sIdentity$ravenwood;
+    private static ThreadLocal<SomeArgs> sIdentity$ravenwood;
 
     /** @hide */
     @android.ravenwood.annotation.RavenwoodKeep
-    public static void init$ravenwood(int uid, int pid) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.argi1 = uid;
-        args.argi2 = pid;
-        sIdentity$ravenwood = args;
+    public static void init$ravenwood(final int uid, final int pid) {
+        sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = uid;
+            args.argi2 = pid;
+            args.argi3 = Long.hashCode(Thread.currentThread().getId());
+            args.argi4 = THREAD_PRIORITY_DEFAULT;
+            return args;
+        });
     }
 
     /** @hide */
@@ -870,7 +874,7 @@
 
     /** @hide */
     public static final int myPid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi2;
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
     }
 
     /**
@@ -886,10 +890,16 @@
      * Returns the identifier of the calling thread, which be used with
      * {@link #setThreadPriority(int, int)}.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final int myTid() {
         return Os.gettid();
     }
 
+    /** @hide */
+    public static final int myTid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
+    }
+
     /**
      * Returns the identifier of this process's uid.  This is the kernel uid
      * that the process is running under, which is the identity of its
@@ -903,7 +913,7 @@
 
     /** @hide */
     public static final int myUid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi1;
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
     }
 
     /**
@@ -1086,9 +1096,22 @@
      * not have permission to modify the given thread, or to use the given
      * priority.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void setThreadPriority(int tid, int priority)
             throws IllegalArgumentException, SecurityException;
 
+    /** @hide */
+    public static final void setThreadPriority$ravenwood(int tid, int priority) {
+        final SomeArgs args =
+                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
+        if (args.argi3 == tid) {
+            args.argi4 = priority;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+
     /**
      * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
      * throw an exception if passed a background-level thread priority.  This is only
@@ -1226,9 +1249,15 @@
      *
      * @see #setThreadPriority(int, int)
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void setThreadPriority(int priority)
             throws IllegalArgumentException, SecurityException;
 
+    /** @hide */
+    public static final void setThreadPriority$ravenwood(int priority) {
+        setThreadPriority(myTid(), priority);
+    }
+
     /**
      * Return the current priority of a thread, based on Linux priorities.
      *
@@ -1242,9 +1271,22 @@
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getThreadPriority(int tid)
             throws IllegalArgumentException;
 
+    /** @hide */
+    public static final int getThreadPriority$ravenwood(int tid) {
+        final SomeArgs args =
+                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
+        if (args.argi3 == tid) {
+            return args.argi4;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+
     /**
      * Return the current scheduling policy of a thread, based on Linux.
      *
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index e2a5833..2e6cccb 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.IAlarmManager;
 import android.app.time.UnixEpochTime;
@@ -111,6 +110,11 @@
     private static volatile IAlarmManager sIAlarmManager;
 
     /**
+     * Since {@code nanoTime()} is arbitrary, anchor our Ravenwood clocks against it.
+     */
+    private static final long sAnchorNanoTime$ravenwood = System.nanoTime();
+
+    /**
      * This class is uninstantiable.
      */
     @UnsupportedAppUsage
@@ -194,26 +198,22 @@
 
     /** @hide */
     public static long uptimeMillis$ravenwood() {
-        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
-        return System.currentTimeMillis() - (1672556400L * 1_000)
-                - (DateUtils.WEEK_IN_MILLIS * 1_000);
+        return uptimeNanos() / 1_000_000;
     }
 
     /**
      * Returns nanoseconds since boot, not counting time spent in deep sleep.
      *
      * @return nanoseconds of non-sleep uptime since boot.
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
     @CriticalNative
     @android.ravenwood.annotation.RavenwoodReplace
     public static native long uptimeNanos();
 
     /** @hide */
     public static long uptimeNanos$ravenwood() {
-        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
-        return System.nanoTime() - (1672556400L * 1_000_000_000)
-                - (DateUtils.WEEK_IN_MILLIS * 1_000_000_000);
+        return System.nanoTime() - sAnchorNanoTime$ravenwood;
     }
 
     /**
@@ -242,8 +242,7 @@
 
     /** @hide */
     public static long elapsedRealtime$ravenwood() {
-        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
-        return System.currentTimeMillis() - (1672556400L * 1_000);
+        return elapsedRealtimeNanos() / 1_000_000;
     }
 
     /**
@@ -272,8 +271,8 @@
 
     /** @hide */
     public static long elapsedRealtimeNanos$ravenwood() {
-        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
-        return System.nanoTime() - (1672556400L * 1_000_000_000);
+        // Elapsed realtime is uptime plus an hour that we've been "asleep"
+        return uptimeNanos() + (DateUtils.HOUR_IN_MILLIS * 1_000_000);
     }
 
     /**
diff --git a/core/java/android/os/ThreadLocalWorkSource.java b/core/java/android/os/ThreadLocalWorkSource.java
index e9adb20..7c4a2be 100644
--- a/core/java/android/os/ThreadLocalWorkSource.java
+++ b/core/java/android/os/ThreadLocalWorkSource.java
@@ -37,6 +37,7 @@
  *
  * @hide Only for use within system server.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class ThreadLocalWorkSource {
     public static final int UID_NONE = Message.UID_NONE;
     private static final ThreadLocal<int []> sWorkSourceUid =
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 03a1b6f..3eea94e 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -71,7 +71,8 @@
     static final int COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN = COLUMN_INDEX_UID + 1;
     static final int COLUMN_INDEX_TIME_IN_FOREGROUND = COLUMN_INDEX_UID + 2;
     static final int COLUMN_INDEX_TIME_IN_BACKGROUND = COLUMN_INDEX_UID + 3;
-    static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 4;
+    static final int COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE = COLUMN_INDEX_UID + 4;
+    static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 5;
 
     UidBatteryConsumer(BatteryConsumerData data) {
         super(data);
@@ -92,17 +93,35 @@
 
     /**
      * Returns the amount of time in milliseconds this UID spent in the specified state.
+     * @deprecated use {@link #getTimeInProcessStateMs} instead.
      */
+    @Deprecated
     public long getTimeInStateMs(@State int state) {
         switch (state) {
             case STATE_BACKGROUND:
-                return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND);
+                return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND)
+                        + mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE);
             case STATE_FOREGROUND:
                 return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND);
         }
         return 0;
     }
 
+    /**
+     * Returns the amount of time in milliseconds this UID spent in the specified process state.
+     */
+    public long getTimeInProcessStateMs(@ProcessState int state) {
+        switch (state) {
+            case PROCESS_STATE_BACKGROUND:
+                return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND);
+            case PROCESS_STATE_FOREGROUND:
+                return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND);
+            case PROCESS_STATE_FOREGROUND_SERVICE:
+                return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE);
+        }
+        return 0;
+    }
+
     @Override
     public void dump(PrintWriter pw, boolean skipEmptyComponents) {
         pw.print("UID ");
@@ -158,9 +177,11 @@
                     packageWithHighestDrain);
         }
         serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND,
-                getTimeInStateMs(STATE_FOREGROUND));
+                getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND));
         serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND,
-                getTimeInStateMs(STATE_BACKGROUND));
+                getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND));
+        serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE,
+                getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE));
         mPowerComponents.writeToXml(serializer);
         serializer.endTag(null, BatteryUsageStats.XML_TAG_UID);
     }
@@ -180,10 +201,13 @@
 
         consumerBuilder.setPackageWithHighestDrain(
                 parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE));
-        consumerBuilder.setTimeInStateMs(STATE_FOREGROUND,
+        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND,
                 parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND));
-        consumerBuilder.setTimeInStateMs(STATE_BACKGROUND,
+        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND,
                 parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND));
+        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
+                parser.getAttributeLong(null,
+                        BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE));
         while (!(eventType == XmlPullParser.END_TAG
                 && parser.getName().equals(BatteryUsageStats.XML_TAG_UID))
                 && eventType != XmlPullParser.END_DOCUMENT) {
@@ -255,7 +279,9 @@
         /**
          * Sets the duration, in milliseconds, that this UID was active in a particular state,
          * such as foreground or background.
+         * @deprecated use {@link #setTimeInProcessStateMs} instead.
          */
+        @Deprecated
         @NonNull
         public Builder setTimeInStateMs(@State int state, long timeInStateMs) {
             switch (state) {
@@ -272,6 +298,28 @@
         }
 
         /**
+         * Sets the duration, in milliseconds, that this UID was active in a particular process
+         * state, such as foreground service.
+         */
+        @NonNull
+        public Builder setTimeInProcessStateMs(@ProcessState int state, long timeInProcessStateMs) {
+            switch (state) {
+                case PROCESS_STATE_FOREGROUND:
+                    mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND, timeInProcessStateMs);
+                    break;
+                case PROCESS_STATE_BACKGROUND:
+                    mData.putLong(COLUMN_INDEX_TIME_IN_BACKGROUND, timeInProcessStateMs);
+                    break;
+                case PROCESS_STATE_FOREGROUND_SERVICE:
+                    mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE, timeInProcessStateMs);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported process state: " + state);
+            }
+            return this;
+        }
+
+        /**
          * Marks the UidBatteryConsumer for exclusion from the result set.
          */
         public Builder excludeFromBatteryUsageStats() {
@@ -285,12 +333,15 @@
         public Builder add(UidBatteryConsumer consumer) {
             mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents);
 
-            setTimeInStateMs(STATE_FOREGROUND,
+            setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND,
                     mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND)
-                            + consumer.getTimeInStateMs(STATE_FOREGROUND));
-            setTimeInStateMs(STATE_BACKGROUND,
+                            + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND));
+            setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND,
                     mData.getLong(COLUMN_INDEX_TIME_IN_BACKGROUND)
-                            + consumer.getTimeInStateMs(STATE_BACKGROUND));
+                            + consumer.getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND));
+            setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
+                    mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE)
+                            + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE));
 
             if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) {
                 mPackageWithHighestDrain = consumer.getPackageWithHighestDrain();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2419a4c..ec6d20f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -248,7 +248,7 @@
     @SystemApi
     public static final int RESTRICTION_SOURCE_PROFILE_OWNER = 0x4;
 
-    /** @hide */
+    /** @removed mistakenly exposed as system-api previously */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "RESTRICTION_" }, value = {
             RESTRICTION_NOT_SET,
@@ -256,7 +256,6 @@
             RESTRICTION_SOURCE_DEVICE_OWNER,
             RESTRICTION_SOURCE_PROFILE_OWNER
     })
-    @SystemApi
     public @interface UserRestrictionSource {}
 
     /**
@@ -5694,6 +5693,44 @@
     }
 
     /**
+     * Returns the string/label that should be used to represent the context user. For example,
+     * this string can represent a profile in tabbed views. This is only applicable to
+     * {@link #isProfile() profile users}. This string is translated to the device default language.
+     *
+     * @return String representing the label for the context user.
+     *
+     * @throws android.content.res.Resources.NotFoundException if the user does not have a label
+     * defined.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("UnflaggedApi") // b/306636213
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+                    Manifest.permission.MANAGE_USERS,
+                    Manifest.permission.QUERY_USERS,
+                    Manifest.permission.INTERACT_ACROSS_USERS})
+    public @NonNull String getProfileLabel() {
+        if (isManagedProfile(mUserId)) {
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            return dpm.getResources().getString(
+                    android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB,
+                    () -> getDefaultProfileLabel(mUserId));
+        }
+        return getDefaultProfileLabel(mUserId);
+    }
+
+    private String getDefaultProfileLabel(int userId) {
+        try {
+            final int resourceId = mService.getProfileLabelResId(userId);
+            return Resources.getSystem().getString(resourceId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * If the user is a {@link UserManager#isProfile profile}, checks if the user
      * shares media with its parent user (the user that created this profile).
      * Returns false for any other type of user.
diff --git a/core/java/android/os/WorkDuration.aidl b/core/java/android/os/WorkDuration.aidl
deleted file mode 100644
index 0f61204..0000000
--- a/core/java/android/os/WorkDuration.aidl
+++ /dev/null
@@ -1,19 +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 android.os;
-
-parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
deleted file mode 100644
index 4fdc34f..0000000
--- a/core/java/android/os/WorkDuration.java
+++ /dev/null
@@ -1,213 +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 android.os;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-
-import java.util.Objects;
-
-/**
- * WorkDuration contains the measured time in nano seconds of the workload
- * in each component, see
- * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
- *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
- */
-@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
-public final class WorkDuration implements Parcelable {
-    long mWorkPeriodStartTimestampNanos = 0;
-    long mActualTotalDurationNanos = 0;
-    long mActualCpuDurationNanos = 0;
-    long mActualGpuDurationNanos = 0;
-    long mTimestampNanos = 0;
-
-    public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() {
-        @Override
-        public WorkDuration createFromParcel(Parcel in) {
-            return new WorkDuration(in);
-        }
-
-        @Override
-        public WorkDuration[] newArray(int size) {
-            return new WorkDuration[size];
-        }
-    };
-
-    public WorkDuration() {}
-
-    public WorkDuration(long workPeriodStartTimestampNanos,
-                      long actualTotalDurationNanos,
-                      long actualCpuDurationNanos,
-                      long actualGpuDurationNanos) {
-        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
-        mActualTotalDurationNanos = actualTotalDurationNanos;
-        mActualCpuDurationNanos = actualCpuDurationNanos;
-        mActualGpuDurationNanos = actualGpuDurationNanos;
-    }
-
-    /**
-     * @hide
-     */
-    public WorkDuration(long workPeriodStartTimestampNanos,
-                      long actualTotalDurationNanos,
-                      long actualCpuDurationNanos,
-                      long actualGpuDurationNanos,
-                      long timestampNanos) {
-        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
-        mActualTotalDurationNanos = actualTotalDurationNanos;
-        mActualCpuDurationNanos = actualCpuDurationNanos;
-        mActualGpuDurationNanos = actualGpuDurationNanos;
-        mTimestampNanos = timestampNanos;
-    }
-
-    WorkDuration(@NonNull Parcel in) {
-        mWorkPeriodStartTimestampNanos = in.readLong();
-        mActualTotalDurationNanos = in.readLong();
-        mActualCpuDurationNanos = in.readLong();
-        mActualGpuDurationNanos = in.readLong();
-        mTimestampNanos = in.readLong();
-    }
-
-    /**
-     * Sets the work period start timestamp in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
-        if (workPeriodStartTimestampNanos <= 0) {
-            throw new IllegalArgumentException(
-                "the work period start timestamp should be positive.");
-        }
-        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
-    }
-
-    /**
-     * Sets the actual total duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
-        if (actualTotalDurationNanos <= 0) {
-            throw new IllegalArgumentException("the actual total duration should be positive.");
-        }
-        mActualTotalDurationNanos = actualTotalDurationNanos;
-    }
-
-    /**
-     * Sets the actual CPU duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
-        if (actualCpuDurationNanos <= 0) {
-            throw new IllegalArgumentException("the actual CPU duration should be positive.");
-        }
-        mActualCpuDurationNanos = actualCpuDurationNanos;
-    }
-
-    /**
-     * Sets the actual GPU duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
-        if (actualGpuDurationNanos < 0) {
-            throw new IllegalArgumentException("the actual GPU duration should be non negative.");
-        }
-        mActualGpuDurationNanos = actualGpuDurationNanos;
-    }
-
-    /**
-     * Returns the work period start timestamp based in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public long getWorkPeriodStartTimestampNanos() {
-        return mWorkPeriodStartTimestampNanos;
-    }
-
-    /**
-     * Returns the actual total duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public long getActualTotalDurationNanos() {
-        return mActualTotalDurationNanos;
-    }
-
-    /**
-     * Returns the actual CPU duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public long getActualCpuDurationNanos() {
-        return mActualCpuDurationNanos;
-    }
-
-    /**
-     * Returns the actual GPU duration in nanoseconds.
-     *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
-     */
-    public long getActualGpuDurationNanos() {
-        return mActualGpuDurationNanos;
-    }
-
-    /**
-     * @hide
-     */
-    public long getTimestampNanos() {
-        return mTimestampNanos;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeLong(mWorkPeriodStartTimestampNanos);
-        dest.writeLong(mActualTotalDurationNanos);
-        dest.writeLong(mActualCpuDurationNanos);
-        dest.writeLong(mActualGpuDurationNanos);
-        dest.writeLong(mTimestampNanos);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof WorkDuration)) {
-            return false;
-        }
-        WorkDuration workDuration = (WorkDuration) obj;
-        return workDuration.mTimestampNanos == this.mTimestampNanos
-            && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos
-            && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos
-            && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos
-            && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos,
-                            mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos);
-    }
-}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d405d1d..a78f221 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -61,11 +61,4 @@
     namespace: "backstage_power"
     description: "Guards a new API in PowerManager to check if battery saver is supported or not."
     bug: "305067031"
-}
-
-flag {
-    name: "adpf_gpu_report_actual_work_duration"
-    namespace: "game"
-    description: "Guards the ADPF GPU APIs."
-    bug: "284324521"
-}
+}
\ No newline at end of file
diff --git a/core/java/android/permission/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl
index cc52a72..c68c0c9 100644
--- a/core/java/android/permission/IOnPermissionsChangeListener.aidl
+++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl
@@ -21,5 +21,5 @@
  * {@hide}
  */
 oneway interface IOnPermissionsChangeListener {
-    void onPermissionsChanged(int uid);
+    void onPermissionsChanged(int uid, String persistentDeviceId);
 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e10ea10..91adc37 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1725,7 +1725,7 @@
     }
 
     private final class OnPermissionsChangeListenerDelegate
-            extends IOnPermissionsChangeListener.Stub implements Handler.Callback{
+            extends IOnPermissionsChangeListener.Stub implements Handler.Callback {
         private static final int MSG_PERMISSIONS_CHANGED = 1;
 
         private final PackageManager.OnPermissionsChangedListener mListener;
@@ -1738,8 +1738,9 @@
         }
 
         @Override
-        public void onPermissionsChanged(int uid) {
-            mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
+        public void onPermissionsChanged(int uid, String persistentDeviceId) {
+            mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId)
+                    .sendToTarget();
         }
 
         @Override
@@ -1747,7 +1748,8 @@
             switch (msg.what) {
                 case MSG_PERMISSIONS_CHANGED: {
                     final int uid = msg.arg1;
-                    mListener.onPermissionsChanged(uid);
+                    final String persistentDeviceId = msg.obj.toString();
+                    mListener.onPermissionsChanged(uid, persistentDeviceId);
                     return true;
                 }
                 default:
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index fb2ac711..5cbc18e 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -17,6 +17,7 @@
 
 flag {
     name: "role_controller_in_system_server"
+    is_fixed_read_only: true
     namespace: "permissions"
     description: "enable role controller in system server"
     bug: "302562590"
@@ -55,4 +56,11 @@
   namespace: "permissions"
   description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER"
   bug: "222650148"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "factory_reset_prep_permission_apis"
+  namespace: "wallet_integration"
+  description: "enable Permission PREPARE_FACTORY_RESET."
+  bug: "302016478"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33c15d77..8f18c5f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -36,6 +37,7 @@
 import android.app.AppOpsManager;
 import android.app.Application;
 import android.app.AutomaticZenRule;
+import android.app.Flags;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.SearchManager;
@@ -1904,6 +1906,36 @@
             = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
 
     /**
+     * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode.
+     * <p>
+     * Users can change the behavior of the mode when it's activated and access the owning app's
+     * additional configuration screen, where triggering criteria can be modified (see
+     * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}).
+     * <p>
+     * A matching Activity will only be found if
+     * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
+     * <p>
+     * Input: Intent's data URI set with an application name, using the "package" schema (like
+     * "package:com.my.app").
+     * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+            = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
+
+    /**
+     * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
+            = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
+
+    /**
      * Activity Action: Show settings for video captioning.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -14943,6 +14975,38 @@
         public static final String APP_OPS_CONSTANTS = "app_ops_constants";
 
         /**
+         * Device Idle (Doze) specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "inactive_to=60000,sensing_to=400000"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * inactive_to                      (long)
+         * sensing_to                       (long)
+         * motion_inactive_to               (long)
+         * idle_after_inactive_to           (long)
+         * idle_pending_to                  (long)
+         * max_idle_pending_to              (long)
+         * idle_pending_factor              (float)
+         * quick_doze_delay_to              (long)
+         * idle_to                          (long)
+         * max_idle_to                      (long)
+         * idle_factor                      (float)
+         * min_time_to_alarm                (long)
+         * max_temp_app_whitelist_duration  (long)
+         * notification_whitelist_duration  (long)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.DeviceIdleController.Constants
+         */
+        public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
+
+        /**
          * Battery Saver specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 9bdd0c2..0133bd8 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -36,10 +36,3 @@
     description: "Collect sepolicy hash from sysfs"
     bug: "308471499"
 }
-
-flag {
-    name: "extend_ecm_to_all_settings"
-    namespace: "responsible_apis"
-    description: "Allow all app settings to be restrictable via configuration"
-    bug: "297372999"
-}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
new file mode 100644
index 0000000..4e5588c
--- /dev/null
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -0,0 +1,22 @@
+package: "android.security"
+
+flag {
+    name: "extend_ecm_to_all_settings"
+    namespace: "responsible_apis"
+    description: "Allow all app settings to be restrictable via configuration"
+    bug: "297372999"
+}
+
+flag {
+    name: "asm_restrictions_enabled"
+    namespace: "responsible_apis"
+    description: "Enables ASM restrictions for activity starts and finishes"
+    bug: "230590090"
+}
+
+flag {
+    name: "asm_toasts_enabled"
+    namespace: "responsible_apis"
+    description: "Enables toasts when ASM restrictions are triggered"
+    bug: "230590090"
+}
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
new file mode 100644
index 0000000..5978383
--- /dev/null
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.chooser"
+
+flag {
+  name: "support_nfc_resolver"
+  namespace: "systemui"
+  description: "This flag controls the new NFC 'resolver' activity"
+  bug: "268089816"
+}
+
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 4d33bfd..d76fa5b 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -16,8 +16,11 @@
 
 package android.service.notification;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Flags;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Parcel;
@@ -57,7 +60,6 @@
      * Indicates that Do Not Disturb should be turned on.
      */
     public static final int STATE_TRUE = 1;
-
     public static final int STATE_UNKNOWN = 2;
     public static final int STATE_ERROR = 3;
 
@@ -90,6 +92,33 @@
     public final int flags;
     public final int icon;
 
+    /** @hide */
+    @IntDef(prefix = { "SOURCE_" }, value = {
+            SOURCE_UNKNOWN,
+            SOURCE_USER_ACTION,
+            SOURCE_SCHEDULE,
+            SOURCE_CONTEXT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Source {}
+
+    /** The state is changing due to an unknown reason. */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int SOURCE_UNKNOWN = 0;
+    /** The state is changing due to an explicit user action. */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int SOURCE_USER_ACTION = 1;
+    /** The state is changing due to an automatic schedule (alarm, set time, etc). */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int SOURCE_SCHEDULE = 2;
+    /** The state is changing due to a change in context (such as detected driving or sleeping). */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int SOURCE_CONTEXT = 3;
+
+    /** The source of, or reason for, the state change represented by this Condition. **/
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public final @Source int source;
+
     /**
      * The maximum string length for any string contained in this condition.
      * @hide
@@ -99,14 +128,48 @@
     /**
      * An object representing the current state of a {@link android.app.AutomaticZenRule}.
      * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
-     * @param summary a user visible description of the rule state.
+     * @param summary a user visible description of the rule state
+     * @param state whether the mode should be activated or deactivated
      */
+    // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source.
     public Condition(Uri id, String summary, int state) {
-        this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS);
+        this(id, summary, "", "", -1, state, SOURCE_UNKNOWN, FLAG_RELEVANT_ALWAYS);
     }
 
+    /**
+     * An object representing the current state of a {@link android.app.AutomaticZenRule}.
+     * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
+     * @param summary a user visible description of the rule state
+     * @param state whether the mode should be activated or deactivated
+     * @param source the source of, or reason for, the state change represented by this Condition
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public Condition(@Nullable Uri id, @Nullable String summary, @State int state,
+                     @Source int source) {
+        this(id, summary, "", "", -1, state, source, FLAG_RELEVANT_ALWAYS);
+    }
+
+    // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source.
     public Condition(Uri id, String summary, String line1, String line2, int icon,
             int state, int flags) {
+        this(id, summary, line1, line2, icon, state, SOURCE_UNKNOWN, flags);
+    }
+
+    /**
+     * An object representing the current state of a {@link android.app.AutomaticZenRule}.
+     * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
+     * @param summary a user visible description of the rule state
+     * @param line1 a user-visible description of when the rule will end
+     * @param line2 a continuation of the user-visible description of when the rule will end
+     * @param icon an icon representing this condition
+     * @param state whether the mode should be activated or deactivated
+     * @param source the source of, or reason for, the state change represented by this Condition
+     * @param flags flags on this condition
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public Condition(@Nullable Uri id, @Nullable String summary, @Nullable String line1,
+                     @Nullable String line2, int icon, @State int state, @Source int source,
+                     int flags) {
         if (id == null) throw new IllegalArgumentException("id is required");
         if (summary == null) throw new IllegalArgumentException("summary is required");
         if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
@@ -116,6 +179,7 @@
         this.line2 = getTrimmedString(line2);
         this.icon = icon;
         this.state = state;
+        this.source = source;
         this.flags = flags;
     }
 
@@ -129,6 +193,7 @@
                 source.readString(),
                 source.readInt(),
                 source.readInt(),
+                Flags.modesApi() ? source.readInt() : SOURCE_UNKNOWN,
                 source.readInt());
     }
 
@@ -144,20 +209,27 @@
         dest.writeString(line2);
         dest.writeInt(icon);
         dest.writeInt(state);
+        if (Flags.modesApi()) {
+            dest.writeInt(this.source);
+        }
         dest.writeInt(this.flags);
     }
 
     @Override
     public String toString() {
-        return new StringBuilder(Condition.class.getSimpleName()).append('[')
+        StringBuilder sb = new StringBuilder(Condition.class.getSimpleName()).append('[')
                 .append("state=").append(stateToString(state))
                 .append(",id=").append(id)
                 .append(",summary=").append(summary)
                 .append(",line1=").append(line1)
                 .append(",line2=").append(line2)
-                .append(",icon=").append(icon)
-                .append(",flags=").append(flags)
+                .append(",icon=").append(icon);
+        if (Flags.modesApi()) {
+            sb.append(",source=").append(sourceToString(source));
+        }
+        return sb.append(",flags=").append(flags)
                 .append(']').toString();
+
     }
 
     /** @hide */
@@ -171,6 +243,7 @@
         proto.write(ConditionProto.LINE_2, line2);
         proto.write(ConditionProto.ICON, icon);
         proto.write(ConditionProto.STATE, state);
+        // TODO: b/310644464 - Add source to dump.
         proto.write(ConditionProto.FLAGS, flags);
 
         proto.end(token);
@@ -184,6 +257,16 @@
         throw new IllegalArgumentException("state is invalid: " + state);
     }
 
+    /** Provides a human-readable string version of the Source enum. */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static @NonNull String sourceToString(@Source int source) {
+        if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN";
+        if (source == SOURCE_USER_ACTION) return "SOURCE_USER_ACTION";
+        if (source == SOURCE_SCHEDULE) return "SOURCE_SCHEDULE";
+        if (source == SOURCE_CONTEXT) return "SOURCE_CONTEXT";
+        throw new IllegalArgumentException("source is invalid: " + source);
+    }
+
     public static String relevanceToString(int flags) {
         final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
         final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
@@ -197,17 +280,24 @@
         if (!(o instanceof Condition)) return false;
         if (o == this) return true;
         final Condition other = (Condition) o;
-        return Objects.equals(other.id, id)
+        boolean finalEquals = Objects.equals(other.id, id)
                 && Objects.equals(other.summary, summary)
                 && Objects.equals(other.line1, line1)
                 && Objects.equals(other.line2, line2)
                 && other.icon == icon
                 && other.state == state
                 && other.flags == flags;
+        if (Flags.modesApi()) {
+            return finalEquals && other.source == source;
+        }
+        return finalEquals;
     }
 
     @Override
     public int hashCode() {
+        if (Flags.modesApi()) {
+            return Objects.hash(id, summary, line1, line2, icon, state, source, flags);
+        }
         return Objects.hash(id, summary, line1, line2, icon, state, flags);
     }
 
diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS
index bb0e6ab..cb0b5fa 100644
--- a/core/java/android/service/notification/OWNERS
+++ b/core/java/android/service/notification/OWNERS
@@ -2,6 +2,7 @@
 
 juliacr@google.com
 yurilin@google.com
+matiashe@google.com
 jeffdq@google.com
 dsandler@android.com
 dsandler@google.com
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 5b096c6..db0b7ff 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -178,6 +178,16 @@
         return mMaximizeDoze;
     }
 
+    /**
+     * Whether any of the effects are set up.
+     * @hide
+     */
+    public boolean hasEffects() {
+        return mGrayscale || mSuppressAmbientDisplay || mDimWallpaper || mNightMode
+                || mDisableAutoBrightness || mDisableTapToWake || mDisableTiltToWake
+                || mDisableTouch || mMinimizeRadioUsage || mMaximizeDoze;
+    }
+
     /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */
     @NonNull
     public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 305b751..c486b6a 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -25,6 +25,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -114,6 +115,7 @@
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true;
     private static final boolean DEFAULT_ALLOW_CONV = true;
     private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+    private static final boolean DEFAULT_ALLOW_PRIORITY_CHANNELS = true;
     private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
     // Default setting here is 010011101 = 157
     private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS =
@@ -140,6 +142,7 @@
     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
     private static final String ALLOW_ATT_CONV = "convos";
     private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
+    private static final String ALLOW_ATT_CHANNELS = "channels";
     private static final String DISALLOW_TAG = "disallow";
     private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
     private static final String STATE_TAG = "state";
@@ -160,6 +163,7 @@
     private static final String CONDITION_ATT_LINE2 = "line2";
     private static final String CONDITION_ATT_ICON = "icon";
     private static final String CONDITION_ATT_STATE = "state";
+    private static final String CONDITION_ATT_SOURCE = "source";
     private static final String CONDITION_ATT_FLAGS = "flags";
 
     private static final String ZEN_POLICY_TAG = "zen_policy";
@@ -184,6 +188,18 @@
     private static final String RULE_ATT_ICON = "rule_icon";
     private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
 
+    private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
+    private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
+            "zdeSuppressAmbientDisplay";
+    private static final String DEVICE_EFFECT_DIM_WALLPAPER = "zdeDimWallpaper";
+    private static final String DEVICE_EFFECT_USE_NIGHT_MODE = "zdeUseNightMode";
+    private static final String DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS = "zdeDisableAutoBrightness";
+    private static final String DEVICE_EFFECT_DISABLE_TAP_TO_WAKE = "zdeDisableTapToWake";
+    private static final String DEVICE_EFFECT_DISABLE_TILT_TO_WAKE = "zdeDisableTiltToWake";
+    private static final String DEVICE_EFFECT_DISABLE_TOUCH = "zdeDisableTouch";
+    private static final String DEVICE_EFFECT_MINIMIZE_RADIO_USAGE = "zdeMinimizeRadioUsage";
+    private static final String DEVICE_EFFECT_MAXIMIZE_DOZE = "zdeMaximizeDoze";
+
     @UnsupportedAppUsage
     public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
     public boolean allowMedia = DEFAULT_ALLOW_MEDIA;
@@ -199,7 +215,12 @@
     public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM;
     public int user = UserHandle.USER_SYSTEM;
     public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
+    // Note that when the modes_api flag is true, the areChannelsBypassingDnd boolean only tracks
+    // whether the current user has any priority channels. These channels may bypass DND when
+    // allowPriorityChannels is true.
+    // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined.
     public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
+    public boolean allowPriorityChannels = DEFAULT_ALLOW_PRIORITY_CHANNELS;
     public int version;
 
     public ZenRule manualRule;
@@ -207,7 +228,8 @@
     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
 
     @UnsupportedAppUsage
-    public ZenModeConfig() { }
+    public ZenModeConfig() {
+    }
 
     public ZenModeConfig(Parcel source) {
         allowCalls = source.readInt() == 1;
@@ -236,6 +258,9 @@
         areChannelsBypassingDnd = source.readInt() == 1;
         allowConversations = source.readBoolean();
         allowConversationsFrom = source.readInt();
+        if (Flags.modesApi()) {
+            allowPriorityChannels = source.readBoolean();
+        }
     }
 
     @Override
@@ -270,11 +295,14 @@
         dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
         dest.writeBoolean(allowConversations);
         dest.writeInt(allowConversationsFrom);
+        if (Flags.modesApi()) {
+            dest.writeBoolean(allowPriorityChannels);
+        }
     }
 
     @Override
     public String toString() {
-        return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
+        StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
                 .append("user=").append(user)
                 .append(",allowAlarms=").append(allowAlarms)
                 .append(",allowMedia=").append(allowMedia)
@@ -289,9 +317,14 @@
                 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
                 .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString
                         (allowConversationsFrom))
-                .append(",suppressedVisualEffects=").append(suppressedVisualEffects)
-                .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd)
-                .append(",\nautomaticRules=").append(rulesToString())
+                .append(",suppressedVisualEffects=").append(suppressedVisualEffects);
+        if (Flags.modesApi()) {
+            sb.append(",hasPriorityChannels=").append(areChannelsBypassingDnd);
+            sb.append(",allowPriorityChannels=").append(allowPriorityChannels);
+        } else {
+            sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
+        }
+        return sb.append(",\nautomaticRules=").append(rulesToString())
                 .append(",\nmanualRule=").append(manualRule)
                 .append(']').toString();
     }
@@ -371,7 +404,7 @@
         if (!(o instanceof ZenModeConfig)) return false;
         if (o == this) return true;
         final ZenModeConfig other = (ZenModeConfig) o;
-        return other.allowAlarms == allowAlarms
+        boolean eq = other.allowAlarms == allowAlarms
                 && other.allowMedia == allowMedia
                 && other.allowSystem == allowSystem
                 && other.allowCalls == allowCalls
@@ -388,10 +421,22 @@
                 && other.areChannelsBypassingDnd == areChannelsBypassingDnd
                 && other.allowConversations == allowConversations
                 && other.allowConversationsFrom == allowConversationsFrom;
+        if (Flags.modesApi()) {
+            return eq && other.allowPriorityChannels == allowPriorityChannels;
+        }
+        return eq;
     }
 
     @Override
     public int hashCode() {
+        if (Flags.modesApi()) {
+            return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
+                    allowRepeatCallers, allowMessages,
+                    allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
+                    user, automaticRules, manualRule,
+                    suppressedVisualEffects, areChannelsBypassingDnd, allowConversations,
+                    allowConversationsFrom, allowPriorityChannels);
+        }
         return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
                 allowRepeatCallers, allowMessages,
                 allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
@@ -497,6 +542,10 @@
                     rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA,
                             DEFAULT_ALLOW_MEDIA);
                     rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM);
+                    if (Flags.modesApi()) {
+                        rt.allowPriorityChannels = safeBoolean(parser, ALLOW_ATT_CHANNELS,
+                                DEFAULT_ALLOW_PRIORITY_CHANNELS);
+                    }
 
                     // migrate old suppressed visual effects fields, if they still exist in the xml
                     Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF);
@@ -570,6 +619,9 @@
         out.attributeBoolean(null, ALLOW_ATT_SYSTEM, allowSystem);
         out.attributeBoolean(null, ALLOW_ATT_CONV, allowConversations);
         out.attributeInt(null, ALLOW_ATT_CONV_FROM, allowConversationsFrom);
+        if (Flags.modesApi()) {
+            out.attributeBoolean(null, ALLOW_ATT_CHANNELS, allowPriorityChannels);
+        }
         out.endTag(null, ALLOW_TAG);
 
         out.startTag(null, DISALLOW_TAG);
@@ -629,6 +681,7 @@
         rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false);
         rt.zenPolicy = readZenPolicyXml(parser);
         if (Flags.modesApi()) {
+            rt.zenDeviceEffects = readZenDeviceEffectsXml(parser);
             rt.allowManualInvocation = safeBoolean(parser, RULE_ATT_ALLOW_MANUAL, false);
             rt.iconResId = safeInt(parser, RULE_ATT_ICON, 0);
             rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
@@ -666,6 +719,9 @@
         if (rule.zenPolicy != null) {
             writeZenPolicyXml(rule.zenPolicy, out);
         }
+        if (Flags.modesApi() && rule.zenDeviceEffects != null) {
+            writeZenDeviceEffectsXml(rule.zenDeviceEffects, out);
+        }
         out.attributeBoolean(null, RULE_ATT_MODIFIED, rule.modified);
         if (Flags.modesApi()) {
             out.attributeBoolean(null, RULE_ATT_ALLOW_MANUAL, rule.allowManualInvocation);
@@ -687,7 +743,12 @@
         final int state = safeInt(parser, CONDITION_ATT_STATE, -1);
         final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1);
         try {
-            return new Condition(id, summary, line1, line2, icon, state, flags);
+            if (Flags.modesApi()) {
+                final int source = safeInt(parser, CONDITION_ATT_SOURCE, Condition.SOURCE_UNKNOWN);
+                return new Condition(id, summary, line1, line2, icon, state, source, flags);
+            } else {
+                return new Condition(id, summary, line1, line2, icon, state, flags);
+            }
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Unable to read condition xml", e);
             return null;
@@ -701,6 +762,9 @@
         out.attribute(null, CONDITION_ATT_LINE2, c.line2);
         out.attributeInt(null, CONDITION_ATT_ICON, c.icon);
         out.attributeInt(null, CONDITION_ATT_STATE, c.state);
+        if (Flags.modesApi()) {
+            out.attributeInt(null, CONDITION_ATT_SOURCE, c.source);
+        }
         out.attributeInt(null, CONDITION_ATT_FLAGS, c.flags);
     }
 
@@ -722,6 +786,13 @@
         final int system = safeInt(parser, ALLOW_ATT_SYSTEM, ZenPolicy.STATE_UNSET);
         final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET);
         final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET);
+        if (Flags.modesApi()) {
+            final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.CHANNEL_TYPE_UNSET);
+            if (channels != ZenPolicy.CHANNEL_TYPE_UNSET) {
+                builder.allowChannels(channels);
+                policySet = true;
+            }
+        }
 
         if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
             builder.allowCalls(calls);
@@ -830,6 +901,10 @@
         writeZenPolicyState(SHOW_ATT_AMBIENT, policy.getVisualEffectAmbient(), out);
         writeZenPolicyState(SHOW_ATT_NOTIFICATION_LIST, policy.getVisualEffectNotificationList(),
                 out);
+
+        if (Flags.modesApi()) {
+            writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getAllowedChannels(), out);
+        }
     }
 
     private static void writeZenPolicyState(String attr, int val, TypedXmlSerializer out)
@@ -843,6 +918,10 @@
             if (val != ZenPolicy.CONVERSATION_SENDERS_UNSET) {
                 out.attributeInt(null, attr, val);
             }
+        } else if (Flags.modesApi() && Objects.equals(attr, ALLOW_ATT_CHANNELS)) {
+            if (val != ZenPolicy.CHANNEL_TYPE_UNSET) {
+                out.attributeInt(null, attr, val);
+            }
         } else {
             if (val != ZenPolicy.STATE_UNSET) {
                 out.attributeInt(null, attr, val);
@@ -850,6 +929,57 @@
         }
     }
 
+    @Nullable
+    private static ZenDeviceEffects readZenDeviceEffectsXml(TypedXmlPullParser parser) {
+        ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(
+                        safeBoolean(parser, DEVICE_EFFECT_DISPLAY_GRAYSCALE, false))
+                .setShouldSuppressAmbientDisplay(
+                        safeBoolean(parser, DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY, false))
+                .setShouldDimWallpaper(safeBoolean(parser, DEVICE_EFFECT_DIM_WALLPAPER, false))
+                .setShouldUseNightMode(safeBoolean(parser, DEVICE_EFFECT_USE_NIGHT_MODE, false))
+                .setShouldDisableAutoBrightness(
+                        safeBoolean(parser, DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS, false))
+                .setShouldDisableTapToWake(
+                        safeBoolean(parser, DEVICE_EFFECT_DISABLE_TAP_TO_WAKE, false))
+                .setShouldDisableTiltToWake(
+                        safeBoolean(parser, DEVICE_EFFECT_DISABLE_TILT_TO_WAKE, false))
+                .setShouldDisableTouch(safeBoolean(parser, DEVICE_EFFECT_DISABLE_TOUCH, false))
+                .setShouldMinimizeRadioUsage(
+                        safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
+                .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
+                .build();
+
+        return deviceEffects.hasEffects() ? deviceEffects : null;
+    }
+
+    private static void writeZenDeviceEffectsXml(ZenDeviceEffects deviceEffects,
+            TypedXmlSerializer out) throws IOException {
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DISPLAY_GRAYSCALE,
+                deviceEffects.shouldDisplayGrayscale());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY,
+                deviceEffects.shouldSuppressAmbientDisplay());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DIM_WALLPAPER, deviceEffects.shouldDimWallpaper());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_USE_NIGHT_MODE, deviceEffects.shouldUseNightMode());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS,
+                deviceEffects.shouldDisableAutoBrightness());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TAP_TO_WAKE,
+                deviceEffects.shouldDisableTapToWake());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TILT_TO_WAKE,
+                deviceEffects.shouldDisableTiltToWake());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TOUCH, deviceEffects.shouldDisableTouch());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
+                deviceEffects.shouldMinimizeRadioUsage());
+        writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
+    }
+
+    private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
+            throws IOException {
+        if (value) {
+            out.attributeBoolean(null, att, true);
+        }
+    }
+
     public static boolean isValidHour(int val) {
         return val >= 0 && val < 24;
     }
@@ -967,6 +1097,11 @@
             builder.showInNotificationList(
                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0);
         }
+
+        if (Flags.modesApi()) {
+            builder.allowChannels(allowPriorityChannels ? ZenPolicy.CHANNEL_TYPE_PRIORITY
+                    : ZenPolicy.CHANNEL_TYPE_NONE);
+        }
         return builder.build();
     }
 
@@ -1092,8 +1227,15 @@
             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
         }
 
+        int state = defaultPolicy.state;
+        if (Flags.modesApi()) {
+            state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
+                    getAllowPriorityChannelsWithDefault(zenPolicy.getAllowedChannels(),
+                            DEFAULT_ALLOW_PRIORITY_CHANNELS));
+        }
+
         return new NotificationManager.Policy(priorityCategories, callSenders,
-                messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders);
+                messageSenders, suppressedVisualEffects, state, conversationSenders);
     }
 
     private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) {
@@ -1131,6 +1273,24 @@
     }
 
     /**
+     * Gets whether priority channels are permitted by this channel type, with the specified
+     * default if the value is unset. This effectively converts the channel enum to a boolean,
+     * where "true" indicates priority channels are allowed to break through and "false" means
+     * they are not.
+     */
+    public static boolean getAllowPriorityChannelsWithDefault(
+            @ZenPolicy.ChannelType int channelType, boolean defaultAllowChannels) {
+        switch (channelType) {
+            case ZenPolicy.CHANNEL_TYPE_PRIORITY:
+                return true;
+            case ZenPolicy.CHANNEL_TYPE_NONE:
+                return false;
+            default:
+                return defaultAllowChannels;
+        }
+    }
+
+    /**
      * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
      */
     public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
@@ -1182,10 +1342,13 @@
         priorityConversationSenders = getConversationSendersWithDefault(
                 allowConversationsFrom, priorityConversationSenders);
 
+        int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
+        if (Flags.modesApi()) {
+            state = Policy.policyState(areChannelsBypassingDnd, allowPriorityChannels);
+        }
+
         return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
-                suppressedVisualEffects, areChannelsBypassingDnd
-                ? Policy.STATE_CHANNELS_BYPASSING_DND : 0,
-                priorityConversationSenders);
+                suppressedVisualEffects, state, priorityConversationSenders);
     }
 
     /**
@@ -1265,6 +1428,9 @@
                 allowConversationsFrom);
         if (policy.state != Policy.STATE_UNSET) {
             areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+            if (Flags.modesApi()) {
+                allowPriorityChannels = policy.allowPriorityChannels();
+            }
         }
     }
 
@@ -1746,6 +1912,8 @@
         // package name, only used for manual rules when they have turned DND on.
         public String enabler;
         public ZenPolicy zenPolicy;
+        @FlaggedApi(Flags.FLAG_MODES_API)
+        @Nullable public ZenDeviceEffects zenDeviceEffects;
         public boolean modified;    // rule has been modified from initial creation
         public String pkg;
         public int type = AutomaticZenRule.TYPE_UNKNOWN;
@@ -1775,6 +1943,9 @@
                 enabler = source.readString();
             }
             zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+            if (Flags.modesApi()) {
+                zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
+            }
             modified = source.readInt() == 1;
             pkg = source.readString();
             if (Flags.modesApi()) {
@@ -1819,6 +1990,9 @@
                 dest.writeInt(0);
             }
             dest.writeParcelable(zenPolicy, 0);
+            if (Flags.modesApi()) {
+                dest.writeParcelable(zenDeviceEffects, 0);
+            }
             dest.writeInt(modified ? 1 : 0);
             dest.writeString(pkg);
             if (Flags.modesApi()) {
@@ -1850,7 +2024,8 @@
                     .append(",condition=").append(condition);
 
             if (Flags.modesApi()) {
-                sb.append(",allowManualInvocation=").append(allowManualInvocation)
+                sb.append(",deviceEffects=").append(zenDeviceEffects)
+                        .append(",allowManualInvocation=").append(allowManualInvocation)
                         .append(",iconResId=").append(iconResId)
                         .append(",triggerDescription=").append(triggerDescription)
                         .append(",type=").append(type);
@@ -1908,6 +2083,7 @@
 
             if (Flags.modesApi()) {
                 return finalEquals
+                        && Objects.equals(other.zenDeviceEffects, zenDeviceEffects)
                         && other.allowManualInvocation == allowManualInvocation
                         && other.iconResId == iconResId
                         && Objects.equals(other.triggerDescription, triggerDescription)
@@ -1921,8 +2097,9 @@
         public int hashCode() {
             if (Flags.modesApi()) {
                 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
-                        component, configurationActivity, pkg, id, enabler, zenPolicy, modified,
-                        allowManualInvocation, iconResId, triggerDescription, type);
+                        component, configurationActivity, pkg, id, enabler, zenPolicy,
+                        zenDeviceEffects, modified, allowManualInvocation, iconResId,
+                        triggerDescription, type);
             }
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                     component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
@@ -1979,7 +2156,11 @@
         boolean allowConversations = (policy.priorityConversationSenders
                 & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
         boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
-        boolean allowSystem =  (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
+        if (Flags.modesApi()) {
+            areChannelsBypassingDnd = policy.hasPriorityChannels()
+                    && policy.allowPriorityChannels();
+        }
+        boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
         return !allowReminders && !allowCalls && !allowMessages && !allowEvents
                 && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem
                 && !allowConversations;
@@ -2010,9 +2191,14 @@
      * This includes notification, ringer and system sounds
      */
     public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) {
+        boolean areChannelsBypassingDnd = config.areChannelsBypassingDnd;
+        if (Flags.modesApi()) {
+            areChannelsBypassingDnd = config.areChannelsBypassingDnd
+                    && config.allowPriorityChannels;
+        }
         return !config.allowReminders && !config.allowCalls && !config.allowMessages
                 && !config.allowEvents && !config.allowRepeatCallers
-                && !config.areChannelsBypassingDnd && !config.allowSystem;
+                && !areChannelsBypassingDnd && !config.allowSystem;
     }
 
     /**
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index eb55e40..9538df1 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.app.Flags;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -221,6 +222,7 @@
         public static final String FIELD_ALLOW_CONVERSATIONS_FROM = "allowConversationsFrom";
         public static final String FIELD_SUPPRESSED_VISUAL_EFFECTS = "suppressedVisualEffects";
         public static final String FIELD_ARE_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
+        public static final String FIELD_ALLOW_PRIORITY_CHANNELS = "allowPriorityChannels";
         private static final Set<String> PEOPLE_TYPE_FIELDS =
                 Set.of(FIELD_ALLOW_CALLS_FROM, FIELD_ALLOW_MESSAGES_FROM);
 
@@ -297,6 +299,12 @@
                 addField(FIELD_ARE_CHANNELS_BYPASSING_DND,
                         new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
             }
+            if (Flags.modesApi()) {
+                if (from.allowPriorityChannels != to.allowPriorityChannels) {
+                    addField(FIELD_ALLOW_PRIORITY_CHANNELS,
+                            new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels));
+                }
+            }
 
             // Compare automatic and manual rules
             final ArraySet<String> allRules = new ArraySet<>();
@@ -452,11 +460,12 @@
         public static final String FIELD_CREATION_TIME = "creationTime";
         public static final String FIELD_ENABLER = "enabler";
         public static final String FIELD_ZEN_POLICY = "zenPolicy";
+        public static final String FIELD_ZEN_DEVICE_EFFECTS = "zenDeviceEffects";
         public static final String FIELD_MODIFIED = "modified";
         public static final String FIELD_PKG = "pkg";
         public static final String FIELD_ALLOW_MANUAL = "allowManualInvocation";
         public static final String FIELD_ICON_RES = "iconResId";
-        public static final String FIELD_TRIGGER = "triggerDescription";
+        public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
         public static final String FIELD_TYPE = "type";
         // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
 
@@ -534,19 +543,25 @@
             if (!Objects.equals(from.pkg, to.pkg)) {
                 addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
             }
-            if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
-                addField(FIELD_TRIGGER,
-                        new FieldDiff<>(from.triggerDescription, to.triggerDescription));
-            }
-            if (from.type != to.type) {
-                addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
-            }
-            if (from.allowManualInvocation != to.allowManualInvocation) {
-                addField(FIELD_ALLOW_MANUAL,
-                        new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
-            }
-            if (!Objects.equals(from.iconResId, to.iconResId)) {
-                addField(FIELD_ICON_RES, new FieldDiff(from.iconResId, to.iconResId));
+            if (android.app.Flags.modesApi()) {
+                if (!Objects.equals(from.zenDeviceEffects, to.zenDeviceEffects)) {
+                    addField(FIELD_ZEN_DEVICE_EFFECTS,
+                            new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects));
+                }
+                if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
+                    addField(FIELD_TRIGGER_DESCRIPTION,
+                            new FieldDiff<>(from.triggerDescription, to.triggerDescription));
+                }
+                if (from.type != to.type) {
+                    addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
+                }
+                if (from.allowManualInvocation != to.allowManualInvocation) {
+                    addField(FIELD_ALLOW_MANUAL,
+                            new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
+                }
+                if (!Objects.equals(from.iconResId, to.iconResId)) {
+                    addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResId, to.iconResId));
+                }
             }
         }
 
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index bffa660..3a4a0c5 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -16,9 +16,11 @@
 
 package android.service.notification;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Flags;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.os.Parcel;
@@ -44,6 +46,7 @@
     private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
     private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
     private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
+    private @ChannelType int mAllowChannels = CHANNEL_TYPE_UNSET;
 
     /** @hide */
     @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -213,6 +216,36 @@
     public static final int STATE_DISALLOW = 2;
 
     /** @hide */
+    @IntDef(prefix = { "CHANNEL_TYPE_" }, value = {
+            CHANNEL_TYPE_UNSET,
+            CHANNEL_TYPE_PRIORITY,
+            CHANNEL_TYPE_NONE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ChannelType {}
+
+    /**
+     * Indicates no explicit setting for which channels may bypass DND when this policy is active.
+     * Defaults to {@link #CHANNEL_TYPE_PRIORITY}.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int CHANNEL_TYPE_UNSET = 0;
+
+    /**
+     * Indicates that channels marked as {@link NotificationChannel#canBypassDnd()} can bypass DND
+     * when this policy is active.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int CHANNEL_TYPE_PRIORITY = 1;
+
+    /**
+     * Indicates that no channels can bypass DND when this policy is active, even those marked as
+     * {@link NotificationChannel#canBypassDnd()}.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int CHANNEL_TYPE_NONE = 2;
+
+    /** @hide */
     public ZenPolicy() {
         mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
         mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
@@ -396,6 +429,19 @@
     }
 
     /**
+     * Which types of {@link NotificationChannel channels} this policy allows to bypass DND. When
+     * this value is {@link #CHANNEL_TYPE_PRIORITY priority} channels, any channel with
+     * canBypassDnd() may bypass DND; when it is {@link #CHANNEL_TYPE_NONE none}, even channels
+     * with canBypassDnd() will be intercepted.
+     * @return {@link #CHANNEL_TYPE_UNSET}, {@link #CHANNEL_TYPE_PRIORITY}, or
+     *   {@link #CHANNEL_TYPE_NONE}
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public @ChannelType int getAllowedChannels() {
+        return mAllowChannels;
+    }
+
+    /**
      * Whether this policy hides all visual effects
      * @hide
      */
@@ -795,6 +841,15 @@
             }
             return this;
         }
+
+        /**
+         * Set whether priority channels are permitted to break through DND.
+         */
+        @FlaggedApi(Flags.FLAG_MODES_API)
+        public @NonNull Builder allowChannels(@ChannelType int channelType) {
+            mZenPolicy.mAllowChannels = channelType;
+            return this;
+        }
     }
 
     @Override
@@ -809,6 +864,9 @@
         dest.writeInt(mPriorityCalls);
         dest.writeInt(mPriorityMessages);
         dest.writeInt(mConversationSenders);
+        if (Flags.modesApi()) {
+            dest.writeInt(mAllowChannels);
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ZenPolicy> CREATOR =
@@ -825,6 +883,9 @@
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
+            if (Flags.modesApi()) {
+                policy.mAllowChannels = source.readInt();
+            }
             return policy;
         }
 
@@ -836,16 +897,18 @@
 
     @Override
     public String toString() {
-        return new StringBuilder(ZenPolicy.class.getSimpleName())
+        StringBuilder sb = new StringBuilder(ZenPolicy.class.getSimpleName())
                 .append('{')
                 .append("priorityCategories=[").append(priorityCategoriesToString())
                 .append("], visualEffects=[").append(visualEffectsToString())
                 .append("], priorityCallsSenders=").append(peopleTypeToString(mPriorityCalls))
                 .append(", priorityMessagesSenders=").append(peopleTypeToString(mPriorityMessages))
                 .append(", priorityConversationSenders=").append(
-                        conversationTypeToString(mConversationSenders))
-                .append('}')
-                .toString();
+                        conversationTypeToString(mConversationSenders));
+        if (Flags.modesApi()) {
+            sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
+        }
+        return sb.append('}').toString();
     }
 
     // Returns a list containing the first maxLength elements of the input list if the list is
@@ -975,21 +1038,45 @@
         return "invalidConversationType{" + conversationType + "}";
     }
 
+    /**
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static String channelTypeToString(@ChannelType int channelType) {
+        switch (channelType) {
+            case CHANNEL_TYPE_UNSET:
+                return "unset";
+            case CHANNEL_TYPE_PRIORITY:
+                return "priority";
+            case CHANNEL_TYPE_NONE:
+                return "none";
+        }
+        return "invalidChannelType{" + channelType + "}";
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (!(o instanceof ZenPolicy)) return false;
         if (o == this) return true;
         final ZenPolicy other = (ZenPolicy) o;
 
-        return Objects.equals(other.mPriorityCategories, mPriorityCategories)
+        boolean eq = Objects.equals(other.mPriorityCategories, mPriorityCategories)
                 && Objects.equals(other.mVisualEffects, mVisualEffects)
                 && other.mPriorityCalls == mPriorityCalls
                 && other.mPriorityMessages == mPriorityMessages
                 && other.mConversationSenders == mConversationSenders;
+        if (Flags.modesApi()) {
+            return eq && other.mAllowChannels == mAllowChannels;
+        }
+        return eq;
     }
 
     @Override
     public int hashCode() {
+        if (Flags.modesApi()) {
+            return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
+                    mPriorityMessages, mConversationSenders, mAllowChannels);
+        }
         return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
                 mConversationSenders);
     }
@@ -1064,7 +1151,10 @@
     }
 
     /**
-     * Applies another policy on top of this policy
+     * Applies another policy on top of this policy. For each field, the resulting policy will have
+     * most restrictive setting that is set of the two policies (if only one has a field set, the
+     * result will inherit that policy's setting).
+     *
      * @hide
      */
     public void apply(ZenPolicy policyToApply) {
@@ -1107,6 +1197,15 @@
                 mVisualEffects.set(visualEffect, policyToApply.mVisualEffects.get(visualEffect));
             }
         }
+
+        // apply allowed channels
+        if (Flags.modesApi()) {
+            // if no channels are allowed, can't newly allow them
+            if (mAllowChannels != CHANNEL_TYPE_NONE
+                    && policyToApply.mAllowChannels != CHANNEL_TYPE_UNSET) {
+                mAllowChannels = policyToApply.mAllowChannels;
+            }
+        }
     }
 
     /**
@@ -1139,9 +1238,10 @@
 
     /**
      * Converts a policy to a statsd proto.
-     * @hides
+     * @hide
      */
     public byte[] toProto() {
+        // TODO: b/308672510 - log new ZenPolicy fields to DNDPolicyProto.
         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
         ProtoOutputStream proto = new ProtoOutputStream(bytes);
 
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 9167153..6da3206 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -66,6 +66,7 @@
      */
     public static final int FLASH_LOCK_LOCKED = 1;
 
+    /** @removed mistakenly exposed previously */
     @IntDef(prefix = { "FLASH_LOCK_" }, value = {
             FLASH_LOCK_UNKNOWN,
             FLASH_LOCK_LOCKED,
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
index 21a8eab..e5910ea 100644
--- a/core/java/android/service/timezone/TEST_MAPPING
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests",
       "options": [
@@ -8,7 +7,10 @@
           "include-filter": "android.service."
         }
       ]
-    },
+    }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
     {
       "name": "CtsLocationTimeZoneManagerHostTest"
     }
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 42203d4..c716cd2 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -387,7 +387,6 @@
                 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this));
     };
 
-
     private void onShutdownInternal() {
         onShutdown();
         // Stop any active recognitions when shutting down.
@@ -1025,6 +1024,26 @@
         }
     }
 
+    /** Set sandboxed detection training data egress op.
+     *
+     * <p> This method can be called by a preinstalled assistant to allow/disallow training data
+     * egress from trusted process.
+     *
+     * @return whether was able to update sandboxed detection op successfully.
+     * @throws SecurityException if assistant is not a preinstalled assistant.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+    public boolean setSandboxedDetectionTrainingDataOp(int opMode) {
+        Log.i(TAG, "Setting training data egress op-mode to " + opMode);
+        try {
+            return mSystemService.setSandboxedDetectionTrainingDataOp(opMode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
      * pre-bundled system voice models.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index bf09ec1..54116a2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2289,7 +2289,7 @@
                         mInputEventReceiver = null;
                     }
 
-                    mSession.remove(mWindow);
+                    mSession.remove(mWindow.asBinder());
                 } catch (RemoteException e) {
                 }
                 mSurfaceHolder.mSurface.release();
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0063d13..886727e 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -490,16 +490,32 @@
     /**
      * Notify changes to activity state changes on certain subscription.
      *
+     * @param subId for which data activity state changed.
+     * @param dataActivityType indicates the latest data activity type e.g. {@link
+     * TelephonyManager#DATA_ACTIVITY_IN}
+     */
+    public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
+        try {
+            sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
+        } catch (RemoteException ex) {
+            // system process is dead
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notify changes to activity state changes on certain subscription.
+     *
      * @param slotIndex for which data activity changed. Can be derived from subId except
      * when subId is invalid.
      * @param subId for which data activity state changed.
-     * @param dataActivityType indicates the latest data activity type e.g, {@link
+     * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
      */
     public void notifyDataActivityChanged(int slotIndex, int subId,
             @DataActivityType int dataActivityType) {
         try {
-            sRegistry.notifyDataActivityForSubscriber(slotIndex, subId, dataActivityType);
+            sRegistry.notifyDataActivityForSubscriberWithSlot(slotIndex, subId, dataActivityType);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 77e616b..2105420b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -118,6 +118,7 @@
             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
             b.mLineBreakConfig = LineBreakConfig.NONE;
+            b.mMinimumFontMetrics = null;
             return b;
         }
 
@@ -130,6 +131,7 @@
             b.mText = null;
             b.mLeftIndents = null;
             b.mRightIndents = null;
+            b.mMinimumFontMetrics = null;
             sPool.release(b);
         }
 
@@ -139,6 +141,7 @@
             mPaint = null;
             mLeftIndents = null;
             mRightIndents = null;
+            mMinimumFontMetrics = null;
         }
 
         public Builder setText(CharSequence source) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index c0a5629..6ea462e 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -68,6 +68,7 @@
 import android.text.style.URLSpan;
 import android.text.style.UnderlineSpan;
 import android.text.style.UpdateAppearance;
+import android.util.EmptyArray;
 import android.util.Log;
 import android.util.Printer;
 import android.view.View;
@@ -141,9 +142,9 @@
         return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
     }
 
-
     private TextUtils() { /* cannot be instantiated */ }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void getChars(CharSequence s, int start, int end,
                                 char[] dest, int destoff) {
         Class<? extends CharSequence> c = s.getClass();
@@ -162,10 +163,12 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch) {
         return indexOf(s, ch, 0);
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch, int start) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -175,6 +178,7 @@
         return indexOf(s, ch, start, s.length());
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch, int start, int end) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -212,10 +216,12 @@
         return -1;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch) {
         return lastIndexOf(s, ch, s.length() - 1);
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch, int last) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -225,6 +231,7 @@
         return lastIndexOf(s, ch, 0, last);
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch,
                                   int start, int last) {
         if (last < 0)
@@ -270,14 +277,17 @@
         return -1;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle) {
         return indexOf(s, needle, 0, s.length());
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle, int start) {
         return indexOf(s, needle, start, s.length());
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle,
                               int start, int end) {
         int nlen = needle.length();
@@ -305,6 +315,7 @@
         return -1;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean regionMatches(CharSequence one, int toffset,
                                         CharSequence two, int ooffset,
                                         int len) {
@@ -337,6 +348,7 @@
      * in that it does not preserve any style runs in the source sequence,
      * allowing a more efficient implementation.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String substring(CharSequence source, int start, int end) {
         if (source instanceof String)
             return ((String) source).substring(start, end);
@@ -409,6 +421,7 @@
      *     calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
      *     tokens is an empty array, an empty string will be returned.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String join(@NonNull CharSequence delimiter, @NonNull Object[] tokens) {
         final int length = tokens.length;
         if (length == 0) {
@@ -432,6 +445,7 @@
      *     calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
      *     tokens is empty, an empty string will be returned.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String join(@NonNull CharSequence delimiter, @NonNull Iterable tokens) {
         final Iterator<?> it = tokens.iterator();
         if (!it.hasNext()) {
@@ -464,9 +478,10 @@
      *
      * @throws NullPointerException if expression or text is null
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String[] split(String text, String expression) {
         if (text.length() == 0) {
-            return EMPTY_STRING_ARRAY;
+            return EmptyArray.STRING;
         } else {
             return text.split(expression, -1);
         }
@@ -489,9 +504,10 @@
      *
      * @throws NullPointerException if expression or text is null
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String[] split(String text, Pattern pattern) {
         if (text.length() == 0) {
-            return EMPTY_STRING_ARRAY;
+            return EmptyArray.STRING;
         } else {
             return pattern.split(text, -1);
         }
@@ -526,6 +542,7 @@
      * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b,"</tt> on
      * comma will return <tt>"a", "b"</tt>, not <tt>"a", "b", ""</tt>.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class SimpleStringSplitter implements StringSplitter, Iterator<String> {
         private String mString;
         private char mDelimiter;
@@ -589,26 +606,31 @@
      * @param str the string to be examined
      * @return true if str is null or zero length
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isEmpty(@Nullable CharSequence str) {
         return str == null || str.length() == 0;
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String nullIfEmpty(@Nullable String str) {
         return isEmpty(str) ? null : str;
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String emptyIfNull(@Nullable String str) {
         return str == null ? "" : str;
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String firstNotEmpty(@Nullable String a, @NonNull String b) {
         return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int length(@Nullable String s) {
         return s != null ? s.length() : 0;
     }
@@ -617,6 +639,7 @@
      * @return interned string if it's null.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String safeIntern(String s) {
         return (s != null) ? s.intern() : null;
     }
@@ -626,6 +649,7 @@
      * spaces and ASCII control characters were trimmed from the start and end,
      * as by {@link String#trim}.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getTrimmedLength(CharSequence s) {
         int len = s.length();
 
@@ -650,6 +674,7 @@
      * @param b second CharSequence to check
      * @return true if a and b are equal
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean equals(CharSequence a, CharSequence b) {
         if (a == b) return true;
         int length;
@@ -1679,6 +1704,7 @@
         return true;
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     /* package */ static char[] obtain(int len) {
         char[] buf;
 
@@ -1693,6 +1719,11 @@
         return buf;
     }
 
+    /* package */ static char[] obtain$ravenwood(int len) {
+        return new char[len];
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
     /* package */ static void recycle(char[] temp) {
         if (temp.length > 1000)
             return;
@@ -1702,11 +1733,16 @@
         }
     }
 
+    /* package */ static void recycle$ravenwood(char[] temp) {
+        // Handled by typical GC
+    }
+
     /**
      * Html-encode the string.
      * @param s the string to be encoded
      * @return the encoded string
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String htmlEncode(String s) {
         StringBuilder sb = new StringBuilder();
         char c;
@@ -1793,6 +1829,7 @@
     /**
      * Returns whether the given CharSequence contains any printable characters.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isGraphic(CharSequence str) {
         final int len = str.length();
         for (int cp, i=0; i<len; i+=Character.charCount(cp)) {
@@ -1819,6 +1856,7 @@
      * @deprecated Use {@link #isGraphic(CharSequence)} instead.
      */
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isGraphic(char c) {
         int gc = Character.getType(c);
         return     gc != Character.CONTROL
@@ -1833,6 +1871,7 @@
     /**
      * Returns whether the given CharSequence contains only digits.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isDigitsOnly(CharSequence str) {
         final int len = str.length();
         for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
@@ -1847,6 +1886,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPrintableAscii(final char c) {
         final int asciiFirst = 0x20;
         final int asciiLast = 0x7E;  // included
@@ -1857,6 +1897,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPrintableAsciiOnly(final CharSequence str) {
         final int len = str.length();
         for (int i = 0; i < len; i++) {
@@ -1908,6 +1949,7 @@
      * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
      * {@link #CAP_MODE_SENTENCES}.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getCapsMode(CharSequence cs, int off, int reqModes) {
         if (off < 0) {
             return 0;
@@ -2153,6 +2195,7 @@
      *             match the supported grammar described above.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static @NonNull String formatSimple(@NonNull String format, Object... args) {
         final StringBuilder sb = new StringBuilder(format);
         int j = 0;
@@ -2342,6 +2385,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isNewline(int codePoint) {
         int type = Character.getType(codePoint);
         return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR
@@ -2349,16 +2393,19 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isWhitespace(int codePoint) {
         return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isWhitespaceExceptNewline(int codePoint) {
         return isWhitespace(codePoint) && !isNewline(codePoint);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPunctuation(int codePoint) {
         int type = Character.getType(codePoint);
         return type == Character.CONNECTOR_PUNCTUATION
@@ -2608,6 +2655,4 @@
     private static Object sLock = new Object();
 
     private static char[] sTemp = null;
-
-    private static String[] EMPTY_STRING_ARRAY = new String[]{};
 }
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 43c38f3..a1885ae 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -75,3 +75,10 @@
   description: "A feature flag that implements line break word style auto."
   bug: "280005585"
 }
+
+flag {
+  name: "inter_character_justification"
+  namespace: "text"
+  description: "A feature flag that implement inter character justification."
+  bug: "283193133"
+}
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
index cc33af3..10905e1 100644
--- a/core/java/android/util/DataUnit.java
+++ b/core/java/android/util/DataUnit.java
@@ -32,6 +32,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public enum DataUnit {
     KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
     MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 4654dbf..d2c5975 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -48,6 +48,9 @@
  * They carry a payload of one or more int, long, or String values.  The
  * event-log-tags file defines the payload contents for each type code.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.EventLog_host")
 public class EventLog {
     /** @hide */ public EventLog() {}
 
@@ -416,6 +419,7 @@
     /**
      * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     private static synchronized void readTagsFile() {
         if (sTagCodes != null && sTagNames != null) return;
 
@@ -441,8 +445,7 @@
                 try {
                     int num = Integer.parseInt(m.group(1));
                     String name = m.group(2);
-                    sTagCodes.put(name, num);
-                    sTagNames.put(num, name);
+                    registerTagLocked(num, name);
                 } catch (NumberFormatException e) {
                     Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
                 }
@@ -454,4 +457,20 @@
             try { if (reader != null) reader.close(); } catch (IOException e) {}
         }
     }
+
+    private static void registerTagLocked(int num, String name) {
+        sTagCodes.put(name, num);
+        sTagNames.put(num, name);
+    }
+
+    private static synchronized void readTagsFile$ravenwood() {
+        // TODO: restore parsing logic once we carry into runtime
+        sTagCodes = new HashMap<String, Integer>();
+        sTagNames = new HashMap<Integer, String>();
+
+        // Hard-code a few common tags
+        registerTagLocked(524288, "sysui_action");
+        registerTagLocked(524290, "sysui_count");
+        registerTagLocked(524291, "sysui_histogram");
+    }
 }
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index c04a71c..413ae1f 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -26,6 +26,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class IntArray implements Cloneable {
     private static final int MIN_CAPACITY_INCREMENT = 12;
 
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
index 3101c0d..4c7ef08 100644
--- a/core/java/android/util/LongArray.java
+++ b/core/java/android/util/LongArray.java
@@ -30,6 +30,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LongArray implements Cloneable {
     private static final int MIN_CAPACITY_INCREMENT = 12;
 
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 3aeecca..c0ceb9ea 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -31,6 +31,7 @@
  * @hide
  */
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Slog {
 
     private Slog() {
@@ -216,6 +217,7 @@
      * @see Log#wtf(String, String)
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @android.ravenwood.annotation.RavenwoodThrow
     public static int wtf(@Nullable String tag, @NonNull String msg) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
     }
@@ -223,6 +225,7 @@
     /**
      * Similar to {@link #wtf(String, String)}, but does not output anything to the log.
      */
+    @android.ravenwood.annotation.RavenwoodThrow
     public static void wtfQuiet(@Nullable String tag, @NonNull String msg) {
         Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
     }
@@ -241,6 +244,7 @@
      * @see Log#wtfStack(String, String)
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @android.ravenwood.annotation.RavenwoodThrow
     public static int wtfStack(@Nullable String tag, @NonNull String msg) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
     }
@@ -259,6 +263,7 @@
      *
      * @see Log#wtf(String, Throwable)
      */
+    @android.ravenwood.annotation.RavenwoodThrow
     public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
     }
@@ -279,6 +284,7 @@
      * @see Log#wtf(String, String, Throwable)
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodThrow
     public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
     }
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index d06b0ce..bff8db1 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -41,6 +41,8 @@
 /**
  * A class containing utility methods related to time zones.
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
 public class TimeUtils {
     /** @hide */ public TimeUtils() {}
     /** {@hide} */
@@ -180,6 +182,7 @@
     private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
     private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
 
+    @android.ravenwood.annotation.RavenwoodKeep
     static private int accumField(int amt, int suffix, boolean always, int zeropad) {
         if (amt > 999) {
             int num = 0;
@@ -202,6 +205,7 @@
         return 0;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
             boolean always, int zeropad) {
         if (always || amt > 0) {
@@ -242,6 +246,7 @@
         return pos;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     private static int formatDurationLocked(long duration, int fieldLen) {
         if (sFormatStr.length < fieldLen) {
             sFormatStr = new char[fieldLen];
@@ -314,6 +319,7 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long duration, StringBuilder builder) {
         synchronized (sFormatSync) {
             int len = formatDurationLocked(duration, 0);
@@ -322,6 +328,7 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
         synchronized (sFormatSync) {
             int len = formatDurationLocked(duration, fieldLen);
@@ -331,6 +338,7 @@
 
     /** @hide Just for debugging; not internationalized. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
         synchronized (sFormatSync) {
             int len = formatDurationLocked(duration, fieldLen);
@@ -340,6 +348,7 @@
 
     /** @hide Just for debugging; not internationalized. */
     @TestApi
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String formatDuration(long duration) {
         synchronized (sFormatSync) {
             int len = formatDurationLocked(duration, 0);
@@ -349,11 +358,13 @@
 
     /** @hide Just for debugging; not internationalized. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long duration, PrintWriter pw) {
         formatDuration(duration, pw, 0);
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long time, long now, StringBuilder sb) {
         if (time == 0) {
             sb.append("--");
@@ -363,6 +374,7 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void formatDuration(long time, long now, PrintWriter pw) {
         if (time == 0) {
             pw.print("--");
@@ -372,16 +384,19 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String formatUptime(long time) {
         return formatTime(time, SystemClock.uptimeMillis());
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String formatRealtime(long time) {
         return formatTime(time, SystemClock.elapsedRealtime());
     }
 
     /** @hide Just for debugging; not internationalized. */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String formatTime(long time, long referenceTime) {
         long diff = time - referenceTime;
         if (diff > 0) {
@@ -402,6 +417,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String logTimeOfDay(long millis) {
         Calendar c = Calendar.getInstance();
         if (millis >= 0) {
@@ -413,6 +429,7 @@
     }
 
     /** {@hide} */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String formatForLogging(long millis) {
         if (millis <= 0) {
             return "unknown";
@@ -426,6 +443,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void dumpTime(PrintWriter pw, long time) {
         pw.print(sDumpDateFormat.format(new Date(time)));
     }
@@ -457,6 +475,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
         pw.print(sDumpDateFormat.format(new Date(time)));
         if (time == now) {
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d6535d4..dbacca5 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -59,8 +59,15 @@
     int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out InsetsState insetsState,
             out Rect attachedFrame, out float[] sizeCompatScale);
-    @UnsupportedAppUsage
-    void remove(IWindow window);
+
+    /**
+     * Removes a clientToken from WMS, which includes unlinking the input channel.
+     *
+     * @param clientToken The token that should be removed. This will normally be the IWindow token
+     * for a standard window. It can also be the generic clientToken that was used when calling
+     * grantInputChannel
+     */
+    void remove(IBinder clientToken);
 
     /**
      * Change the parameters of a window.  You supply the
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9f886c8..d131dc9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -69,6 +69,7 @@
     private final String mName;
     private final int mVendorId;
     private final int mProductId;
+    private final int mDeviceBus;
     private final String mDescriptor;
     private final InputDeviceIdentifier mIdentifier;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -468,8 +469,8 @@
      * Called by native code
      */
     private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
-            int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
-            KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
+            int productId, int deviceBus, String descriptor, boolean isExternal, int sources,
+            int keyboardType, KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
             @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone,
             boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor,
             int usiVersionMinor, int associatedDisplayId) {
@@ -479,6 +480,7 @@
         mName = name;
         mVendorId = vendorId;
         mProductId = productId;
+        mDeviceBus = deviceBus;
         mDescriptor = descriptor;
         mIsExternal = isExternal;
         mSources = sources;
@@ -512,6 +514,7 @@
         mName = in.readString();
         mVendorId = in.readInt();
         mProductId = in.readInt();
+        mDeviceBus = in.readInt();
         mDescriptor = in.readString();
         mIsExternal = in.readInt() != 0;
         mSources = in.readInt();
@@ -551,6 +554,7 @@
         private String mName = "";
         private int mVendorId = 0;
         private int mProductId = 0;
+        private int mDeviceBus = 0;
         private String mDescriptor = "";
         private boolean mIsExternal = false;
         private int mSources = 0;
@@ -604,6 +608,12 @@
             return this;
         }
 
+        /** @see InputDevice#getDeviceBus() */
+        public Builder setDeviceBus(int deviceBus) {
+            mDeviceBus = deviceBus;
+            return this;
+        }
+
         /** @see InputDevice#getDescriptor() */
         public Builder setDescriptor(String descriptor) {
             mDescriptor = descriptor;
@@ -705,6 +715,7 @@
                     mName,
                     mVendorId,
                     mProductId,
+                    mDeviceBus,
                     mDescriptor,
                     mIsExternal,
                     mSources,
@@ -847,6 +858,21 @@
     }
 
     /**
+     * Gets the device bus used by given device, if available.
+     * <p>
+     * The device bus is the communication system used for transferring data
+     * (e.g. USB, Bluetooth etc.). This value comes from the kernel (from input.h).
+     * A value of 0 will be assigned where the device bus is not available.
+     * </p>
+     *
+     * @return The device bus of a given device
+     * @hide
+     */
+    public int getDeviceBus() {
+        return mDeviceBus;
+    }
+
+    /**
      * Gets the input device descriptor, which is a stable identifier for an input device.
      * <p>
      * An input device descriptor uniquely identifies an input device.  Its value
@@ -1448,6 +1474,7 @@
         out.writeString(mName);
         out.writeInt(mVendorId);
         out.writeInt(mProductId);
+        out.writeInt(mDeviceBus);
         out.writeString(mDescriptor);
         out.writeInt(mIsExternal ? 1 : 0);
         out.writeInt(mSources);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 1acc384..cabab6c 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -49,8 +49,9 @@
 
     /** The insets source ID of IME */
     public static final int ID_IME = createId(null, 0, ime());
+
     /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
-    static final int ID_IME_CAPTION_BAR =
+    public static final int ID_IME_CAPTION_BAR =
             InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 451a71b..cbbe785 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -436,13 +436,16 @@
         // Jank due to unknown reasons.
         public static final int UNKNOWN = 0x80;
 
-        public JankData(long frameVsyncId, @JankType int jankType) {
+        public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs) {
             this.frameVsyncId = frameVsyncId;
             this.jankType = jankType;
+            this.frameIntervalNs = frameIntervalNs;
+
         }
 
         public final long frameVsyncId;
         public final @JankType int jankType;
+        public final long frameIntervalNs;
     }
 
     /**
@@ -500,9 +503,6 @@
     // be dumped as additional context
     private static volatile boolean sDebugUsageAfterRelease = false;
 
-    static GlobalTransactionWrapper sGlobalTransaction;
-    static long sTransactionNestCount = 0;
-
     private static final NativeAllocationRegistry sRegistry =
             NativeAllocationRegistry.createMalloced(SurfaceControl.class.getClassLoader(),
                     nativeGetNativeSurfaceControlFinalizer());
@@ -856,33 +856,47 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_SELECTION_STRATEGY_"},
-            value = {FRAME_RATE_SELECTION_STRATEGY_SELF,
-                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN})
+            value = {FRAME_RATE_SELECTION_STRATEGY_PROPAGATE,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN,
+                    FRAME_RATE_SELECTION_STRATEGY_SELF})
     public @interface FrameRateSelectionStrategy {}
 
     // From window.h. Keep these in sync.
     /**
      * Default value. The layer uses its own frame rate specifications, assuming it has any
-     * specifications, instead of its parent's.
+     * specifications, instead of its parent's. If it does not have its own frame rate
+     * specifications, it will try to use its parent's. It will propagate its specifications to any
+     * descendants that do not have their own.
+     *
      * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor layer
-     * supersedes this behavior, meaning that this layer will inherit the frame rate specifications
-     * of that ancestor layer.
+     * supersedes this behavior, meaning that this layer will inherit frame rate specifications
+     * regardless of whether it has its own.
      * @hide
      */
-    public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 0;
+    public static final int FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0;
 
     /**
      * The layer's frame rate specifications will propagate to and override those of its descendant
      * layers.
-     * The layer with this strategy has the {@link #FRAME_RATE_SELECTION_STRATEGY_SELF} behavior
-     * for itself. This does mean that any parent or ancestor layer that also has the strategy
-     * {@link FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's
+     *
+     * The layer itself has the {@link #FRAME_RATE_SELECTION_STRATEGY_PROPAGATE} behavior.
+     * Thus, ancestor layer that also has the strategy
+     * {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's
      * frame rate specifications.
      * @hide
      */
     public static final int FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1;
 
     /**
+     * The layer's frame rate specifications will not propagate to its descendant
+     * layers, even if the descendant layer has no frame rate specifications.
+     * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor
+     * layer supersedes this behavior.
+     * @hide
+     */
+    public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 2;
+
+    /**
      * Builder class for {@link SurfaceControl} objects.
      *
      * By default the surface will be hidden, and have "unset" bounds, meaning it can
@@ -1573,54 +1587,30 @@
         return mNativeObject != 0;
     }
 
-    /*
-     * set surface parameters.
-     * needs to be inside open/closeTransaction block
-     */
-
     /** start a transaction
      * @hide
-     */
-    @UnsupportedAppUsage
-    public static void openTransaction() {
-        synchronized (SurfaceControl.class) {
-            if (sGlobalTransaction == null) {
-                sGlobalTransaction = new GlobalTransactionWrapper();
-            }
-            synchronized(SurfaceControl.class) {
-                sTransactionNestCount++;
-            }
-        }
-    }
-
-    /**
-     * Merge the supplied transaction in to the deprecated "global" transaction.
-     * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
-     * <p>
-     * This is a utility for interop with legacy-code and will go away with the Global Transaction.
-     * @hide
+     * @deprecated Use regular Transaction instead.
      */
     @Deprecated
-    public static void mergeToGlobalTransaction(Transaction t) {
-        synchronized(SurfaceControl.class) {
-            sGlobalTransaction.merge(t);
-        }
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+            publicAlternatives = "Use {@code SurfaceControl.Transaction} instead",
+            trackingBug = 247078497)
+    public static void openTransaction() {
+        // TODO(b/247078497): It was used for global transaction (all usages are removed).
+        //  Keep the method declaration to avoid breaking reference from legacy access.
     }
 
     /** end a transaction
      * @hide
+     * @deprecated Use regular Transaction instead.
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+            publicAlternatives = "Use {@code SurfaceControl.Transaction} instead",
+            trackingBug = 247078497)
     public static void closeTransaction() {
-        synchronized(SurfaceControl.class) {
-            if (sTransactionNestCount == 0) {
-                Log.e(TAG,
-                        "Call to SurfaceControl.closeTransaction without matching openTransaction");
-            } else if (--sTransactionNestCount > 0) {
-                return;
-            }
-            sGlobalTransaction.applyGlobalTransaction(false);
-        }
+        // TODO(b/247078497): It was used for global transaction (all usages are removed).
+        //  Keep the method declaration to avoid breaking reference from legacy access.
     }
 
     /**
@@ -3280,7 +3270,8 @@
                         "setCrop", this, sc, "crop=" + crop);
             }
             if (crop != null) {
-                Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
+                Preconditions.checkArgument(crop.isValid(), "Crop " + crop
+                        + " isn't valid");
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
             } else {
@@ -4495,39 +4486,6 @@
     }
 
     /**
-     * As part of eliminating usage of the global Transaction we expose
-     * a SurfaceControl.getGlobalTransaction function. However calling
-     * apply on this global transaction (rather than using closeTransaction)
-     * would be very dangerous. So for the global transaction we use this
-     * subclass of Transaction where the normal apply throws an exception.
-     */
-    private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
-        void applyGlobalTransaction(boolean sync) {
-            applyResizedSurfaces();
-            notifyReparentedSurfaces();
-            nativeApplyTransaction(mNativeObject, sync, /*oneWay*/ false);
-        }
-
-        @Override
-        public void apply(boolean sync) {
-            throw new RuntimeException("Global transaction must be applied from closeTransaction");
-        }
-    }
-
-    /**
-     * This is a refactoring utility function to enable lower levels of code to be refactored
-     * from using the global transaction (and instead use a passed in Transaction) without
-     * having to refactor the higher levels at the same time.
-     * The returned global transaction can't be applied, it must be applied from closeTransaction
-     * Unless you are working on removing Global Transaction usage in the WindowManager, this
-     * probably isn't a good function to use.
-     * @hide
-     */
-    public static Transaction getGlobalTransaction() {
-        return sGlobalTransaction;
-    }
-
-    /**
      * @hide
      */
     public void resize(int w, int h) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0ae14a2..a44a95a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1523,15 +1523,24 @@
                     if (mSurfaceControl == null) return;
 
                     mRTLastReportedPosition.set(left, top, right, bottom);
+                    final float postScaleX = mRTLastReportedPosition.width()
+                            / (float) mRtSurfaceWidth;
+                    final float postScaleY = mRTLastReportedPosition.height()
+                            / (float) mRtSurfaceHeight;
                     onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl,
                             mRTLastReportedPosition.left /*positionLeft*/,
                             mRTLastReportedPosition.top /*positionTop*/,
-                            mRTLastReportedPosition.width()
-                                    / (float) mRtSurfaceWidth /*postScaleX*/,
-                            mRTLastReportedPosition.height()
-                                    / (float) mRtSurfaceHeight /*postScaleY*/);
+                            postScaleX, postScaleY);
 
-                    mRTLastSetCrop.set(clipLeft, clipTop, clipRight, clipBottom);
+                    mRTLastSetCrop.set((int) (clipLeft / postScaleX), (int) (clipTop / postScaleY),
+                            (int) Math.ceil(clipRight / postScaleX),
+                            (int) Math.ceil(clipBottom / postScaleY));
+                    if (DEBUG_POSITION) {
+                        Log.d(TAG, String.format("Setting layer crop = [%d, %d, %d, %d] "
+                                        + "from scale %f, %f", mRTLastSetCrop.left,
+                                mRTLastSetCrop.top, mRTLastSetCrop.right, mRTLastSetCrop.bottom,
+                                postScaleX, postScaleY));
+                    }
                     mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop);
                     if (mRTLastSetCrop.isEmpty()) {
                         mPositionChangedTransaction.hide(mSurfaceControl);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d591f89..d58c07d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33018,7 +33018,7 @@
     }
 
     private float getSizePercentage() {
-        if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
+        if (mResources == null || getVisibility() != VISIBLE) {
             return 0;
         }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 145af2e..7a6c292 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -81,6 +81,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -128,7 +130,6 @@
 import android.graphics.HardwareRenderer;
 import android.graphics.HardwareRenderer.FrameDrawingCallback;
 import android.graphics.HardwareRendererObserver;
-import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -204,7 +205,6 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
@@ -3989,9 +3989,7 @@
         // when the values are applicable.
         setPreferredFrameRate(mPreferredFrameRate);
         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
-        mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-        mLastPreferredFrameRate = mPreferredFrameRate;
         mPreferredFrameRate = 0;
     }
 
@@ -4029,56 +4027,20 @@
     }
 
     private void notifyContentCaptureEvents() {
-        try {
-            if (!isContentCaptureEnabled()) {
-                if (DEBUG_CONTENT_CAPTURE) {
-                    Log.d(mTag, "notifyContentCaptureEvents while disabled");
-                }
-                mAttachInfo.mContentCaptureEvents = null;
-                return;
-            }
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
-            }
-            MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
-                    .getMainContentCaptureSession();
-            for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) {
-                int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i);
-                mainSession.notifyViewTreeEvent(sessionId, /* started= */ true);
-                ArrayList<Object> events = mAttachInfo.mContentCaptureEvents
-                        .valueAt(i);
-                for_each_event: for (int j = 0; j < events.size(); j++) {
-                    Object event = events.get(j);
-                    if (event instanceof AutofillId) {
-                        mainSession.notifyViewDisappeared(sessionId, (AutofillId) event);
-                    } else if (event instanceof View) {
-                        View view = (View) event;
-                        ContentCaptureSession session = view.getContentCaptureSession();
-                        if (session == null) {
-                            Log.w(mTag, "no content capture session on view: " + view);
-                            continue for_each_event;
-                        }
-                        int actualId = session.getId();
-                        if (actualId != sessionId) {
-                            Log.w(mTag, "content capture session mismatch for view (" + view
-                                    + "): was " + sessionId + " before, it's " + actualId + " now");
-                            continue for_each_event;
-                        }
-                        ViewStructure structure = session.newViewStructure(view);
-                        view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
-                        session.notifyViewAppeared(structure);
-                    } else if (event instanceof Insets) {
-                        mainSession.notifyViewInsetsChanged(sessionId, (Insets) event);
-                    } else {
-                        Log.w(mTag, "invalid content capture event: " + event);
-                    }
-                }
-                mainSession.notifyViewTreeEvent(sessionId, /* started= */ false);
+        if (!isContentCaptureEnabled()) {
+            if (DEBUG_CONTENT_CAPTURE) {
+                Log.d(mTag, "notifyContentCaptureEvents while disabled");
             }
             mAttachInfo.mContentCaptureEvents = null;
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            return;
         }
+
+        final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager;
+        if (manager != null && mAttachInfo.mContentCaptureEvents != null) {
+            final MainContentCaptureSession session = manager.getMainContentCaptureSession();
+            session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents);
+        }
+        mAttachInfo.mContentCaptureEvents = null;
     }
 
     private void notifyHolderSurfaceDestroyed() {
@@ -5879,7 +5841,7 @@
             mInputQueue = null;
         }
         try {
-            mWindowSession.remove(mWindow);
+            mWindowSession.remove(mWindow.asBinder());
         } catch (RemoteException e) {
         }
         // Dispose receiver would dispose client InputChannel, too. That could send out a socket
@@ -11980,8 +11942,11 @@
                 ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
 
         try {
-            mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
-                    frameRateCategory, false).apply();
+            if (mLastPreferredFrameRateCategory != frameRateCategory) {
+                mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+                    frameRateCategory, false).applyAsyncUnsafe();
+                mLastPreferredFrameRateCategory = frameRateCategory;
+            }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate category", e);
         }
@@ -12001,8 +11966,11 @@
         }
 
         try {
-            mFrameRateTransaction.setFrameRate(mSurfaceControl,
-                    preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
+            if (mLastPreferredFrameRate != preferredFrameRate) {
+                mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe();
+                mLastPreferredFrameRate = preferredFrameRate;
+            }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
         }
@@ -12029,7 +11997,8 @@
                 || motionEventAction == MotionEvent.ACTION_MOVE
                 || motionEventAction == MotionEvent.ACTION_UP;
         boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
-                || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION;
+                || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION
+                || windowType == TYPE_NOTIFICATION_SHADE || windowType == TYPE_STATUS_BAR;
         // use toolkitSetFrameRate flag to gate the change
         return desiredAction && desiredType && sToolkitSetFrameRateReadOnlyFlagValue;
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 92ce9f7..046ea77 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3301,9 +3301,8 @@
         /**
          * An internal annotation for flags that can be specified to {@link #softInputMode}.
          *
-         * @hide
+         * @removed mistakenly exposed as system-api previously
          */
-        @SystemApi
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = {
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index da31078..d817e6f 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -276,11 +276,11 @@
     }
 
     @Override
-    public void remove(android.view.IWindow window) throws RemoteException {
-        mRealWm.remove(window);
+    public void remove(IBinder clientToken) throws RemoteException {
+        mRealWm.remove(clientToken);
         State state;
         synchronized (this) {
-            state = mStateForWindow.remove(window.asBinder());
+            state = mStateForWindow.remove(clientToken);
         }
         if (state == null) {
             throw new IllegalArgumentException(
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index b220c4d..07ae134 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -17,6 +17,7 @@
 package android.view.accessibility;
 
 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -25,6 +26,7 @@
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -2042,6 +2044,9 @@
      * @return {@code true} if flash notification works properly.
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_FLASH_NOTIFICATION_SYSTEM_API)
+    @TestApi
+    @SystemApi(client = MODULE_LIBRARIES)
     public boolean startFlashNotificationSequence(@NonNull Context context,
             @FlashNotificationReason int reason) {
         final IAccessibilityManager service;
@@ -2071,6 +2076,9 @@
      * @return {@code true} if flash notification stops properly.
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_FLASH_NOTIFICATION_SYSTEM_API)
+    @TestApi
+    @SystemApi(client = MODULE_LIBRARIES)
     public boolean stopFlashNotificationSequence(@NonNull Context context) {
         final IAccessibilityManager service;
         synchronized (mLock) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 43bfe13..53aed49 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,7 +23,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.Hide;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -752,6 +752,7 @@
      *     {@link #isGranularScrollingSupported()} to check if granular scrolling is supported.
      * </p>
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
             "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
 
@@ -2608,6 +2609,7 @@
      * @return True if all scroll actions that could support
      * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public boolean isGranularScrollingSupported() {
         return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING);
     }
@@ -2626,6 +2628,7 @@
      *
      * @throws IllegalStateException If called from an AccessibilityService.
      */
+    @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
     public void setGranularScrollingSupported(boolean granularScrollingSupported) {
         setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING,
                 granularScrollingSupported);
@@ -6119,6 +6122,7 @@
          * This should be used for {@code mItemCount} and
          * {@code mImportantForAccessibilityItemCount} when values for those fields are not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public static final int UNDEFINED = -1;
 
         private int mRowCount;
@@ -6229,8 +6233,8 @@
          *                  the item count is not known.
          * @param importantForAccessibilityItemCount The count of the collection's views considered
          *                                           important for accessibility.
+         * @hide
          */
-        @Hide
         public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
                 int selectionMode, int itemCount, int importantForAccessibilityItemCount) {
             mRowCount = rowCount;
@@ -6287,6 +6291,7 @@
          *
          * @return The count of items, which may be {@code UNDEFINED} if the count is not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public int getItemCount() {
             return mItemCount;
         }
@@ -6297,6 +6302,7 @@
          * @return The count of items important for accessibility, which may be {@code UNDEFINED}
          * if the count is not known.
          */
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public int getImportantForAccessibilityItemCount() {
             return mImportantForAccessibilityItemCount;
         }
@@ -6323,6 +6329,7 @@
          * The builder for CollectionInfo.
          */
 
+        @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
         public static final class Builder {
             private int mRowCount = 0;
             private int mColumnCount = 0;
@@ -6334,6 +6341,7 @@
             /**
              * Creates a new Builder.
              */
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public Builder() {
             }
 
@@ -6343,6 +6351,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setRowCount(int rowCount) {
                 mRowCount = rowCount;
                 return this;
@@ -6354,6 +6363,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setColumnCount(int columnCount) {
                 mColumnCount = columnCount;
                 return this;
@@ -6364,6 +6374,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setHierarchical(boolean hierarchical) {
                 mHierarchical = hierarchical;
                 return this;
@@ -6375,6 +6386,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setSelectionMode(int selectionMode) {
                 mSelectionMode = selectionMode;
                 return this;
@@ -6389,6 +6401,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setItemCount(int itemCount) {
                 mItemCount = itemCount;
                 return this;
@@ -6401,6 +6414,7 @@
              * @return This builder.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo.Builder setImportantForAccessibilityItemCount(
                     int importantForAccessibilityItemCount) {
                 mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
@@ -6411,6 +6425,7 @@
              * Creates a new {@link CollectionInfo} instance.
              */
             @NonNull
+            @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
             public CollectionInfo build() {
                 CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount,
                         mHierarchical);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 5296b99..c337cb4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
 }
 
 flag {
+    namespace: "accessibility"
+    name: "collection_info_item_counts"
+    description: "Fields for total items and the number of important for accessibility items in a collection"
+    bug: "302376158"
+}
+
+flag {
     name: "deduplicate_accessibility_warning_dialog"
     namespace: "accessibility"
     description: "Removes duplicate definition of the accessibility warning dialog."
@@ -25,6 +32,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "flash_notification_system_api"
+    description: "Makes flash notification APIs as system APIs for calling from mainline module"
+    bug: "282821643"
+}
+
+flag {
+    namespace: "accessibility"
     name: "force_invert_color"
     description: "Enable force force-dark for smart inversion and dark theme everywhere"
     bug: "282821643"
@@ -32,6 +46,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "granular_scrolling"
+    description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+    bug: "302376158"
+}
+
+flag {
+    namespace: "accessibility"
     name: "update_always_on_a11y_service"
     description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
     bug: "298869916"
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 5a058ff..a829747 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -18,6 +18,7 @@
 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
 import static android.view.contentcapture.ContentCaptureHelper.toSet;
+import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -52,6 +53,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.RingBuffer;
 import com.android.internal.util.SyncResultReceiver;
 
@@ -495,10 +497,9 @@
     @GuardedBy("mLock")
     private int mFlags;
 
-    // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
-    // held at the Application level
-    @NonNull
-    private final Handler mHandler;
+    @Nullable
+    @GuardedBy("mLock")
+    private Handler mHandler;
 
     @GuardedBy("mLock")
     private MainContentCaptureSession mMainSession;
@@ -562,11 +563,6 @@
 
         if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
 
-        // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
-        // do, then we should optimize it to run the tests after the Choreographer finishes the most
-        // important steps of the frame.
-        mHandler = Handler.createAsync(Looper.getMainLooper());
-
         mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
 
         if (mOptions.contentProtectionOptions.enableReceiver
@@ -594,13 +590,27 @@
     public MainContentCaptureSession getMainContentCaptureSession() {
         synchronized (mLock) {
             if (mMainSession == null) {
-                mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService);
+                mMainSession = new MainContentCaptureSession(
+                        mContext, this, prepareContentCaptureHandler(), mService);
                 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
             }
             return mMainSession;
         }
     }
 
+    @NonNull
+    @GuardedBy("mLock")
+    private Handler prepareContentCaptureHandler() {
+        if (mHandler == null) {
+            if (runOnBackgroundThreadEnabled()) {
+                mHandler = BackgroundThread.getHandler();
+            } else {
+                mHandler = Handler.createAsync(Looper.getMainLooper());
+            }
+        }
+        return mHandler;
+    }
+
     /** @hide */
     @UiThread
     public void onActivityCreated(@NonNull IBinder applicationToken,
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index d9b0f80..14ec14b 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -34,7 +34,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UiThread;
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Insets;
@@ -50,7 +49,10 @@
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.view.View;
+import android.view.ViewStructure;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
 import android.view.contentprotection.ContentProtectionEventProcessor;
@@ -58,6 +60,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
+import com.android.modules.expresslog.Counter;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -66,6 +69,7 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Main session associated with a context.
@@ -79,6 +83,9 @@
 
     private static final String TAG = MainContentCaptureSession.class.getSimpleName();
 
+    private static final String CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID =
+            "content_capture.value_content_capture_wrong_thread_count";
+
     // For readability purposes...
     private static final boolean FORCE_FLUSH = true;
 
@@ -163,6 +170,8 @@
     @Nullable
     private final LocalLog mFlushHistory;
 
+    private final AtomicInteger mWrongThreadCount = new AtomicInteger(0);
+
     /**
      * Binder object used to update the session state.
      */
@@ -207,7 +216,8 @@
             } else {
                 binder = null;
             }
-            mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder));
+            mainSession.mHandler.post(() ->
+                    mainSession.onSessionStarted(resultCode, binder));
         }
     }
 
@@ -244,9 +254,14 @@
     /**
      * Starts this session.
      */
-    @UiThread
     void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
             @NonNull ComponentName component, int flags) {
+        runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags));
+    }
+
+    private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+               @NonNull ComponentName component, int flags) {
+        checkOnContentCaptureThread();
         if (!isContentCaptureEnabled()) return;
 
         if (sVerbose) {
@@ -280,17 +295,15 @@
             Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
         }
     }
-
     @Override
     void onDestroy() {
-        mHandler.removeMessages(MSG_FLUSH);
-        mHandler.post(() -> {
+        clearAndRunOnContentCaptureThread(() -> {
             try {
                 flush(FLUSH_REASON_SESSION_FINISHED);
             } finally {
                 destroySession();
             }
-        });
+        }, MSG_FLUSH);
     }
 
     /**
@@ -302,8 +315,8 @@
      * @hide
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @UiThread
     public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+        checkOnContentCaptureThread();
         if (binder != null) {
             mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
             mDirectServiceVulture = () -> {
@@ -347,13 +360,12 @@
 
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @UiThread
     public void sendEvent(@NonNull ContentCaptureEvent event) {
         sendEvent(event, /* forceFlush= */ false);
     }
 
-    @UiThread
     private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        checkOnContentCaptureThread();
         final int eventType = event.getType();
         if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
         if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
@@ -396,15 +408,15 @@
         }
     }
 
-    @UiThread
     private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+        checkOnContentCaptureThread();
         if (mContentProtectionEventProcessor != null) {
             mContentProtectionEventProcessor.processEvent(event);
         }
     }
 
-    @UiThread
     private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        checkOnContentCaptureThread();
         final int eventType = event.getType();
         final int maxBufferSize = mManager.mOptions.maxBufferSize;
         if (mEvents == null) {
@@ -538,13 +550,13 @@
         flush(flushReason);
     }
 
-    @UiThread
     private boolean hasStarted() {
+        checkOnContentCaptureThread();
         return mState != UNKNOWN_STATE;
     }
 
-    @UiThread
     private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
+        checkOnContentCaptureThread();
         if (sVerbose) {
             Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
                     + ", checkExisting=" + checkExisting);
@@ -588,8 +600,8 @@
         mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
     }
 
-    @UiThread
     private void flushIfNeeded(@FlushReason int reason) {
+        checkOnContentCaptureThread();
         if (mEvents == null || mEvents.isEmpty()) {
             if (sVerbose) Log.v(TAG, "Nothing to flush");
             return;
@@ -600,8 +612,12 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Override
-    @UiThread
     public void flush(@FlushReason int reason) {
+        runOnContentCaptureThread(() -> flushImpl(reason));
+    }
+
+    private void flushImpl(@FlushReason int reason) {
+        checkOnContentCaptureThread();
         if (mEvents == null || mEvents.size() == 0) {
             if (sVerbose) {
                 Log.v(TAG, "Don't flush for empty event buffer.");
@@ -669,8 +685,8 @@
      * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
      */
     @NonNull
-    @UiThread
     private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+        checkOnContentCaptureThread();
         // NOTE: we must save a reference to the current mEvents and then set it to to null,
         // otherwise clearing it would clear it in the receiving side if the service is also local.
         if (mEvents == null) {
@@ -684,14 +700,15 @@
 
     /** hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @UiThread
     public void destroySession() {
+        checkOnContentCaptureThread();
         if (sDebug) {
             Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
                     + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
                     + getDebugState());
         }
 
+        reportWrongThreadMetric();
         try {
             mSystemServerInterface.finishSession(mId);
         } catch (RemoteException e) {
@@ -710,8 +727,8 @@
     // clearings out.
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @UiThread
     public void resetSession(int newState) {
+        checkOnContentCaptureThread();
         if (sVerbose) {
             Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
                     + getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -794,24 +811,26 @@
     // change should also get get rid of the "internalNotifyXXXX" methods above
     void notifyChildSessionStarted(int parentSessionId, int childSessionId,
             @NonNull ContentCaptureContext clientContext) {
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+        runOnContentCaptureThread(
+                () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
                 .setParentSessionId(parentSessionId).setClientContext(clientContext),
                 FORCE_FLUSH));
     }
 
     void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+        runOnContentCaptureThread(
+                () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
                 .setParentSessionId(parentSessionId), FORCE_FLUSH));
     }
 
     void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+        runOnContentCaptureThread(() ->
+                sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
                 .setViewNode(node.mNode)));
     }
 
-    /** Public because is also used by ViewRootImpl */
-    public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
-        mHandler.post(() -> sendEvent(
+    void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
     }
 
@@ -836,52 +855,102 @@
 
         final int startIndex = Selection.getSelectionStart(text);
         final int endIndex = Selection.getSelectionEnd(text);
-        mHandler.post(() -> sendEvent(
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
                         .setAutofillId(id).setText(eventText)
                         .setComposingIndex(composingStart, composingEnd)
                         .setSelectionIndex(startIndex, endIndex)));
     }
 
-    /** Public because is also used by ViewRootImpl */
-    public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+    void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+        runOnContentCaptureThread(() ->
+                sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
                 .setInsets(viewInsets)));
     }
 
-    /** Public because is also used by ViewRootImpl */
-    public void notifyViewTreeEvent(int sessionId, boolean started) {
+    void notifyViewTreeEvent(int sessionId, boolean started) {
         final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
         final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
 
-        mHandler.post(() -> sendEvent(
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, type),
                 disableFlush ? !started : FORCE_FLUSH));
     }
 
     void notifySessionResumed(int sessionId) {
-        mHandler.post(() -> sendEvent(
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
     }
 
     void notifySessionPaused(int sessionId) {
-        mHandler.post(() -> sendEvent(
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
     }
 
     void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+        runOnContentCaptureThread(() ->
+                sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
                 .setClientContext(context), FORCE_FLUSH));
     }
 
     /** public because is also used by ViewRootImpl */
     public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
-        mHandler.post(() -> sendEvent(
+        runOnContentCaptureThread(() -> sendEvent(
                 new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
                 .setBounds(bounds)
         ));
     }
 
+    /** public because is also used by ViewRootImpl */
+    public void notifyContentCaptureEvents(
+            @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+        runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+    }
+
+    private void notifyContentCaptureEventsImpl(
+            @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+        checkOnContentCaptureThread();
+        try {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
+            }
+            for (int i = 0; i < contentCaptureEvents.size(); i++) {
+                int sessionId = contentCaptureEvents.keyAt(i);
+                notifyViewTreeEvent(sessionId, /* started= */ true);
+                ArrayList<Object> events = contentCaptureEvents.valueAt(i);
+                for_each_event: for (int j = 0; j < events.size(); j++) {
+                    Object event = events.get(j);
+                    if (event instanceof AutofillId) {
+                        notifyViewDisappeared(sessionId, (AutofillId) event);
+                    } else if (event instanceof View) {
+                        View view = (View) event;
+                        ContentCaptureSession session = view.getContentCaptureSession();
+                        if (session == null) {
+                            Log.w(TAG, "no content capture session on view: " + view);
+                            continue for_each_event;
+                        }
+                        int actualId = session.getId();
+                        if (actualId != sessionId) {
+                            Log.w(TAG, "content capture session mismatch for view (" + view
+                                    + "): was " + sessionId + " before, it's " + actualId + " now");
+                            continue for_each_event;
+                        }
+                        ViewStructure structure = session.newViewStructure(view);
+                        view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
+                        session.notifyViewAppeared(structure);
+                    } else if (event instanceof Insets) {
+                        notifyViewInsetsChanged(sessionId, (Insets) event);
+                    } else {
+                        Log.w(TAG, "invalid content capture event: " + event);
+                    }
+                }
+                notifyViewTreeEvent(sessionId, /* started= */ false);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
     @Override
     void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
         super.dump(prefix, pw);
@@ -960,17 +1029,14 @@
         return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
     }
 
-    @UiThread
     private boolean isContentProtectionReceiverEnabled() {
         return mManager.mOptions.contentProtectionOptions.enableReceiver;
     }
 
-    @UiThread
     private boolean isContentCaptureReceiverEnabled() {
         return mManager.mOptions.enableReceiver;
     }
 
-    @UiThread
     private boolean isContentProtectionEnabled() {
         // Should not be possible for mComponentName to be null here but check anyway
         // Should not be possible for groups to be empty if receiver is enabled but check anyway
@@ -980,4 +1046,49 @@
                 && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty()
                         || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty());
     }
+
+    /**
+     * Checks that the current work is running on the assigned thread from {@code mHandler} and
+     * count the number of times running on the wrong thread.
+     *
+     * <p>It is not guaranteed that the callers always invoke function from a single thread.
+     * Therefore, accessing internal properties in {@link MainContentCaptureSession} should
+     * always delegate to the assigned thread from {@code mHandler} for synchronization.</p>
+     */
+    private void checkOnContentCaptureThread() {
+        final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread();
+        if (!onContentCaptureThread) {
+            mWrongThreadCount.incrementAndGet();
+            Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread());
+        }
+    }
+
+    /** Reports number of times running on the wrong thread. */
+    private void reportWrongThreadMetric() {
+        Counter.logIncrement(
+                CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0));
+    }
+
+    /**
+     * Ensures that {@code r} will be running on the assigned thread.
+     *
+     * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable.
+     * </p>
+     */
+    private void runOnContentCaptureThread(@NonNull Runnable r) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.post(r);
+        } else {
+            r.run();
+        }
+    }
+
+    private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.removeMessages(what);
+            mHandler.post(r);
+        } else {
+            r.run();
+        }
+    }
 }
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index aaf90bd..858401a9 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UiThread;
 import android.content.ContentCaptureOptions;
 import android.content.pm.ParceledListSlice;
 import android.os.Handler;
@@ -102,7 +101,6 @@
     }
 
     /** Main entry point for {@link ContentCaptureEvent} processing. */
-    @UiThread
     public void processEvent(@NonNull ContentCaptureEvent event) {
         if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
             storeEvent(event);
@@ -112,7 +110,6 @@
         }
     }
 
-    @UiThread
     private void storeEvent(@NonNull ContentCaptureEvent event) {
         // Ensure receiver gets the package name which might not be set
         ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
@@ -121,7 +118,6 @@
         mEventBuffer.append(event);
     }
 
-    @UiThread
     private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
         ViewNode viewNode = event.getViewNode();
         String eventText = ContentProtectionUtils.getEventTextLower(event);
@@ -154,7 +150,6 @@
         }
     }
 
-    @UiThread
     private void loginDetected() {
         if (mLastFlushTime == null
                 || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
@@ -163,13 +158,11 @@
         resetLoginFlags();
     }
 
-    @UiThread
     private void resetLoginFlags() {
         mGroupsAll.forEach(group -> group.mFound = false);
         mAnyGroupFound = false;
     }
 
-    @UiThread
     private void maybeResetLoginFlags() {
         if (mAnyGroupFound) {
             if (mResetLoginRemainingEventsToProcess <= 0) {
@@ -183,7 +176,6 @@
         }
     }
 
-    @UiThread
     private void flush() {
         mLastFlushTime = Instant.now();
 
@@ -192,7 +184,6 @@
         mHandler.post(() -> handlerOnLoginDetected(events));
     }
 
-    @UiThread
     @NonNull
     private ParceledListSlice<ContentCaptureEvent> clearEvents() {
         List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 1bc7353..d4cfd63 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -428,16 +428,25 @@
     ImeTracker LOGGER = new ImeTracker() {
 
         {
-            // Set logging flag initial value.
-            mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
-            // Update logging flag dynamically.
-            SystemProperties.addChangeCallback(() ->
-                    mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
+            // Read initial system properties.
+            reloadSystemProperties();
+            // Update when system properties change.
+            SystemProperties.addChangeCallback(this::reloadSystemProperties);
         }
 
-        /** Whether progress should be logged. */
+        /** Whether {@link #onProgress} calls should be logged. */
         private boolean mLogProgress;
 
+        /** Whether the stack trace at the request call site should be logged. */
+        private boolean mLogStackTrace;
+
+        private void reloadSystemProperties() {
+            mLogProgress = SystemProperties.getBoolean(
+                    "persist.debug.imetracker", false);
+            mLogStackTrace = SystemProperties.getBoolean(
+                    "persist.debug.imerequest.logstacktrace", false);
+        }
+
         @NonNull
         @Override
         public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@@ -447,7 +456,8 @@
                     reason);
 
             Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
-                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason),
+                    mLogStackTrace ? new Throwable() : null);
 
             return token;
         }
@@ -461,7 +471,8 @@
                     reason);
 
             Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
-                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason),
+                    mLogStackTrace ? new Throwable() : null);
 
             return token;
         }
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 1e8718c..7486362 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -22,4 +22,12 @@
     description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions"
     bug: "301713309"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "concurrent_input_methods"
+    namespace: "input_method"
+    description: "Feature flag for concurrent multi-session IME"
+    bug: "284527000"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index e177731..c6bd20c 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -79,4 +79,9 @@
      * Used by Settings to enable/disable multiprocess.
      */
     void enableMultiProcess(boolean enable);
+
+    /**
+     * Used by Settings to get the default WebView package.
+     */
+    WebViewProviderInfo getDefaultWebViewPackage();
 }
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 1b9ff44..8e89541 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -205,6 +207,9 @@
      * Returns whether WebView should run in multiprocess mode.
      */
     public boolean isMultiProcessEnabled() {
+        if (updateServiceV2()) {
+            return true;
+        }
         try {
             return WebViewFactory.getUpdateService().isMultiProcessEnabled();
         } catch (RemoteException e) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index bc7a5fd..e14ae72 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.content.pm.PackageInfo;
 import android.os.Build;
 import android.os.ChildZygoteProcess;
@@ -50,8 +52,8 @@
     private static PackageInfo sPackage;
 
     /**
-     * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
-     * will not be started.
+     * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote will
+     * not be started. Should be removed entirely after we remove the updateServiceV2 flag.
      */
     @GuardedBy("sLock")
     private static boolean sMultiprocessEnabled = false;
@@ -73,11 +75,19 @@
 
     public static boolean isMultiprocessEnabled() {
         synchronized (sLock) {
-            return sMultiprocessEnabled && sPackage != null;
+            if (updateServiceV2()) {
+                return sPackage != null;
+            } else {
+                return sMultiprocessEnabled && sPackage != null;
+            }
         }
     }
 
     public static void setMultiprocessEnabled(boolean enabled) {
+        if (updateServiceV2()) {
+            throw new IllegalStateException(
+                    "setMultiprocessEnabled shouldn't be called if update_service_v2 flag is set.");
+        }
         synchronized (sLock) {
             sMultiprocessEnabled = enabled;
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a919c00..1acebf4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1054,8 +1054,7 @@
     }
 
     private class SetRemoteCollectionItemListAdapterAction extends Action {
-        @NonNull
-        private CompletableFuture<RemoteCollectionItems> mItemsFuture;
+        private @Nullable RemoteCollectionItems mItems;
         final Intent mServiceIntent;
         int mIntentId = -1;
         boolean mIsReplacedIntoAction = false;
@@ -1064,92 +1063,46 @@
                 @NonNull RemoteCollectionItems items) {
             mViewId = id;
             items.setHierarchyRootData(getHierarchyRootData());
-            mItemsFuture = CompletableFuture.completedFuture(items);
+            mItems = items;
             mServiceIntent = null;
         }
 
         SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
             mViewId = id;
-            mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
-            setHierarchyRootData(getHierarchyRootData());
+            mItems = null;
             mServiceIntent = intent;
         }
 
-        private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
-                Intent intent) {
-            if (intent == null) {
-                Log.e(LOG_TAG, "Null intent received when generating adapter future");
-                return CompletableFuture.completedFuture(new RemoteCollectionItems
-                        .Builder().build());
-            }
-
-            final Context context = ActivityThread.currentApplication();
-            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
-
-            context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
-                    result.defaultExecutor(), new ServiceConnection() {
-                        @Override
-                        public void onServiceConnected(ComponentName componentName,
-                                IBinder iBinder) {
-                            RemoteCollectionItems items;
-                            try {
-                                items = IRemoteViewsFactory.Stub.asInterface(iBinder)
-                                        .getRemoteCollectionItems();
-                            } catch (RemoteException re) {
-                                items = new RemoteCollectionItems.Builder().build();
-                                Log.e(LOG_TAG, "Error getting collection items from the factory",
-                                        re);
-                            } finally {
-                                context.unbindService(this);
-                            }
-
-                            result.complete(items);
-                        }
-
-                        @Override
-                        public void onServiceDisconnected(ComponentName componentName) { }
-                    });
-
-            result.completeOnTimeout(
-                    new RemoteCollectionItems.Builder().build(),
-                    MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
-
-            return result;
-        }
-
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             mViewId = parcel.readInt();
             mIntentId = parcel.readInt();
-            mItemsFuture = CompletableFuture.completedFuture(mIntentId != -1
-                    ? null
-                    : new RemoteCollectionItems(parcel, getHierarchyRootData()));
             mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
+            mItems = mServiceIntent != null
+                    ? null
+                    : new RemoteCollectionItems(parcel, getHierarchyRootData());
         }
 
         @Override
         public void setHierarchyRootData(HierarchyRootData rootData) {
-            if (mIntentId == -1) {
-                mItemsFuture = mItemsFuture
-                        .thenApply(rc -> {
-                            rc.setHierarchyRootData(rootData);
-                            return rc;
-                        });
+            if (mItems != null) {
+                mItems.setHierarchyRootData(rootData);
                 return;
             }
 
-            // Set the root data for items in the cache instead
-            mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
+            if (mIntentId != -1) {
+                // Set the root data for items in the cache instead
+                mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
+            }
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mViewId);
             dest.writeInt(mIntentId);
-            if (mIntentId == -1) {
-                RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
-                items.writeToParcel(dest, flags, /* attached= */ true);
-            }
             dest.writeTypedObject(mServiceIntent, flags);
+            if (mItems != null) {
+                mItems.writeToParcel(dest, flags, /* attached= */ true);
+            }
         }
 
         @Override
@@ -1159,7 +1112,9 @@
             if (target == null) return;
 
             RemoteCollectionItems items = mIntentId == -1
-                    ? getCollectionItemsFromFuture(mItemsFuture)
+                    ? mItems == null
+                            ? new RemoteCollectionItems.Builder().build()
+                            : mItems
                     : mCollectionCache.getItemsForId(mIntentId);
 
             // Ensure that we are applying to an AppWidget root
@@ -1216,51 +1171,32 @@
 
         @Override
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
-            items.visitUris(visitor);
-        }
-    }
+            if (mIntentId != -1 || mItems == null) {
+                return;
+            }
 
-    private static RemoteCollectionItems getCollectionItemsFromFuture(
-            CompletableFuture<RemoteCollectionItems> itemsFuture) {
-        RemoteCollectionItems items;
-        try {
-            items = itemsFuture.get();
-        } catch (Exception e) {
-            Log.e(LOG_TAG, "Error getting collection items from future", e);
-            items = new RemoteCollectionItems.Builder().build();
+            mItems.visitUris(visitor);
         }
-
-        return items;
     }
 
     /**
      * @hide
      */
-    public void collectAllIntents() {
-        mCollectionCache.collectAllIntentsNoComplete(this);
+    public CompletableFuture<Void> collectAllIntents() {
+        return mCollectionCache.collectAllIntentsNoComplete(this);
     }
 
     private class RemoteCollectionCache {
         private SparseArray<String> mIdToUriMapping = new SparseArray<>();
         private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
 
-        // We don't put this into the parcel
-        private HashMap<String, CompletableFuture<RemoteCollectionItems>> mTempUriToFutureMapping =
-                new HashMap<>();
-
         RemoteCollectionCache() { }
 
         RemoteCollectionCache(RemoteCollectionCache src) {
-            boolean isWaitingCache = src.mTempUriToFutureMapping.size() != 0;
             for (int i = 0; i < src.mIdToUriMapping.size(); i++) {
                 String uri = src.mIdToUriMapping.valueAt(i);
                 mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri);
-                if (isWaitingCache) {
-                    mTempUriToFutureMapping.put(uri, src.mTempUriToFutureMapping.get(uri));
-                } else {
-                    mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
-                }
+                mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
             }
         }
 
@@ -1281,14 +1217,8 @@
 
         void setHierarchyDataForId(int intentId, HierarchyRootData data) {
             String uri = mIdToUriMapping.get(intentId);
-            if (mTempUriToFutureMapping.get(uri) != null) {
-                CompletableFuture<RemoteCollectionItems> itemsFuture =
-                        mTempUriToFutureMapping.get(uri);
-                mTempUriToFutureMapping.put(uri, itemsFuture.thenApply(rc -> {
-                    rc.setHierarchyRootData(data);
-                    return rc;
-                }));
-
+            if (mUriToCollectionMapping.get(uri) == null) {
+                Log.e(LOG_TAG, "Error setting hierarchy data for id=" + intentId);
                 return;
             }
 
@@ -1301,14 +1231,17 @@
             return mUriToCollectionMapping.get(uri);
         }
 
-        void collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+        CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+            CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
             if (inViews.hasSizedRemoteViews()) {
                 for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
-                    remoteViews.collectAllIntents();
+                    collectionFuture = CompletableFuture.allOf(collectionFuture,
+                            collectAllIntentsNoComplete(remoteViews));
                 }
             } else if (inViews.hasLandscapeAndPortraitLayouts()) {
-                inViews.mLandscape.collectAllIntents();
-                inViews.mPortrait.collectAllIntents();
+                collectionFuture = CompletableFuture.allOf(
+                        collectAllIntentsNoComplete(inViews.mLandscape),
+                        collectAllIntentsNoComplete(inViews.mPortrait));
             } else if (inViews.mActions != null) {
                 for (Action action : inViews.mActions) {
                     if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1318,40 +1251,95 @@
                         }
 
                         if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
-                            String uri = mIdToUriMapping.get(rca.mIntentId);
-                            mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
-                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                            final String uri = mIdToUriMapping.get(rca.mIntentId);
+                            collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
+                                            .thenAccept(rc -> {
+                                                rc.setHierarchyRootData(getHierarchyRootData());
+                                                mUriToCollectionMapping.put(uri, rc);
+                                            }));
+                            rca.mItems = null;
                             continue;
                         }
 
                         // Differentiate between the normal collection actions and the ones with
                         // intents.
                         if (rca.mServiceIntent != null) {
-                            String uri = rca.mServiceIntent.toUri(0);
+                            final String uri = rca.mServiceIntent.toUri(0);
                             int index = mIdToUriMapping.indexOfValue(uri);
                             if (index == -1) {
                                 int newIntentId = mIdToUriMapping.size();
                                 rca.mIntentId = newIntentId;
                                 mIdToUriMapping.put(newIntentId, uri);
-                                // mUriToIntentMapping.put(uri, mServiceIntent);
-                                mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
                             } else {
                                 rca.mIntentId = mIdToUriMapping.keyAt(index);
+                                rca.mItems = null;
+                                continue;
                             }
-                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                            collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
+                                            .thenAccept(rc -> {
+                                                rc.setHierarchyRootData(getHierarchyRootData());
+                                                mUriToCollectionMapping.put(uri, rc);
+                                            }));
+                            rca.mItems = null;
                         } else {
-                            RemoteCollectionItems items = getCollectionItemsFromFuture(
-                                    rca.mItemsFuture);
-                            for (RemoteViews views : items.mViews) {
-                                views.collectAllIntents();
+                            for (RemoteViews views : rca.mItems.mViews) {
+                                collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                        collectAllIntentsNoComplete(views));
                             }
                         }
                     } else if (action instanceof ViewGroupActionAdd vgaa
                             && vgaa.mNestedViews != null) {
-                        vgaa.mNestedViews.collectAllIntents();
+                        collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                collectAllIntentsNoComplete(vgaa.mNestedViews));
                     }
                 }
             }
+
+            return collectionFuture;
+        }
+
+        private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+                Intent intent) {
+            if (intent == null) {
+                Log.e(LOG_TAG, "Null intent received when generating adapter future");
+                return CompletableFuture.completedFuture(new RemoteCollectionItems
+                    .Builder().build());
+            }
+
+            final Context context = ActivityThread.currentApplication();
+            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+            context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+                    result.defaultExecutor(), new ServiceConnection() {
+                        @Override
+                        public void onServiceConnected(ComponentName componentName,
+                                IBinder iBinder) {
+                            RemoteCollectionItems items;
+                            try {
+                                items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+                                    .getRemoteCollectionItems();
+                            } catch (RemoteException re) {
+                                items = new RemoteCollectionItems.Builder().build();
+                                Log.e(LOG_TAG, "Error getting collection items from the factory",
+                                        re);
+                            } finally {
+                                context.unbindService(this);
+                            }
+
+                            result.complete(items);
+                        }
+
+                        @Override
+                        public void onServiceDisconnected(ComponentName componentName) { }
+                    });
+
+            result.completeOnTimeout(
+                    new RemoteCollectionItems.Builder().build(),
+                    MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+            return result;
         }
 
         public void writeToParcel(Parcel out, int flags) {
@@ -1360,10 +1348,7 @@
                 out.writeInt(mIdToUriMapping.keyAt(i));
                 String intentUri = mIdToUriMapping.valueAt(i);
                 out.writeString8(intentUri);
-                RemoteCollectionItems items = mTempUriToFutureMapping.get(intentUri) != null
-                        ? getCollectionItemsFromFuture(mTempUriToFutureMapping.get(intentUri))
-                        : mUriToCollectionMapping.get(intentUri);
-                items.writeToParcel(out, flags, true);
+                mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
             }
         }
     }
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 95e9e86..befb002 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -528,14 +528,12 @@
         private final IBinder mDisplayToken;
         private final int mWidth;
         private final int mHeight;
-        private final boolean mUseIdentityTransform;
 
         private DisplayCaptureArgs(Builder builder) {
             super(builder);
             mDisplayToken = builder.mDisplayToken;
             mWidth = builder.mWidth;
             mHeight = builder.mHeight;
-            mUseIdentityTransform = builder.mUseIdentityTransform;
         }
 
         /**
@@ -545,7 +543,6 @@
             private IBinder mDisplayToken;
             private int mWidth;
             private int mHeight;
-            private boolean mUseIdentityTransform;
 
             /**
              * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
@@ -586,17 +583,6 @@
                 return this;
             }
 
-            /**
-             * Replace the rotation transform of the display with the identity transformation while
-             * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0
-             * orientation. Set this value to false if the screenshot should be taken in the
-             * current screen orientation.
-             */
-            public Builder setUseIdentityTransform(boolean useIdentityTransform) {
-                mUseIdentityTransform = useIdentityTransform;
-                return this;
-            }
-
             @Override
             Builder getThis() {
                 return this;
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 5b0d8d1..cc2329fc 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -20,7 +20,7 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
-import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF;
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -303,7 +303,7 @@
             SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay(
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
-                    FRAME_RATE_SELECTION_STRATEGY_SELF);
+                    FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
             // smoothSwitchOnly is false to request a higher framerate, even if it means switching
             // the display mode will cause would jank on non-VRR devices because keeping a lower
             // refresh rate would mean a poorer user experience.
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 35ce726..34c6399 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -87,6 +88,38 @@
         @NonNull
         public final Matrix transform;
 
+        /**
+         * True if the window is touchable.
+         */
+        @SuppressLint("UnflaggedApi") // The API is only used for tests.
+        public final boolean isTouchable;
+
+        /**
+         * True if the window is focusable.
+         */
+        @SuppressLint("UnflaggedApi") // The API is only used for tests.
+        public final boolean isFocusable;
+
+        /**
+         * True if the window is preventing splitting
+         */
+        @SuppressLint("UnflaggedApi") // The API is only used for tests.
+        public final boolean isPreventSplitting;
+
+        /**
+         * True if the window duplicates touches received to wallpaper.
+         */
+        @SuppressLint("UnflaggedApi") // The API is only used for tests.
+        public final boolean isDuplicateTouchToWallpaper;
+
+        /**
+         * True if the window is listening for when there is a touch DOWN event
+         * occurring outside its touchable bounds. When such an event occurs,
+         * this window will receive a MotionEvent with ACTION_OUTSIDE.
+         */
+        @SuppressLint("UnflaggedApi") // The API is only used for tests.
+        public final boolean isWatchOutsideTouch;
+
         WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId,
                 @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) {
             this.windowToken = windowToken;
@@ -96,6 +129,14 @@
             this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
             this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
             this.transform = transform;
+            this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0;
+            this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0;
+            this.isPreventSplitting = (inputConfig
+                            & InputConfig.PREVENT_SPLITTING) != 0;
+            this.isDuplicateTouchToWallpaper = (inputConfig
+                            & InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0;
+            this.isWatchOutsideTouch = (inputConfig
+                            & InputConfig.WATCH_OUTSIDE_TOUCH) != 0;
         }
 
         @Override
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 94e6009..f828cff 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -26,4 +26,18 @@
     namespace: "responsible_apis"
     description: "Enable toasts to indicate actual BAL blocking."
     bug: "308059069"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "bal_return_correct_code_if_caller_is_persistent_system_process"
+    namespace: "responsible_apis"
+    description: "Split visibility check and return a better status code in case of system process."
+    bug: "171459802"
+}
+
+flag {
+    name: "bal_improve_real_caller_visibility_check"
+    namespace: "responsible_apis"
+    description: "Prevent a task to restart based on a visible window during task switch."
+    bug: "171459802"
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 68eddff..29932f3 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -32,3 +32,27 @@
     description: "Enable public API for Window Surfaces"
     bug: "287076178"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "delete_capture_display"
+    description: "Delete uses of ScreenCapture#captureDisplay"
+    is_fixed_read_only: true
+    bug: "293445881"
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "allow_disable_activity_record_input_sink"
+    description: "Whether to allow system activity to disable ActivityRecordInputSink"
+    is_fixed_read_only: true
+    bug: "262477923"
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "secure_window_state"
+    description: "Move SC secure flag to WindowState level"
+    is_fixed_read_only: true
+    bug: "308662081"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 4705dc5..31a3ebd 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+  name: "edge_to_edge_by_default"
+  namespace: "windowing_frontend"
+  description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
+  bug: "309578419"
+}
+
+flag {
     name: "defer_display_updates"
     namespace: "window_manager"
     description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b600b22..933cc49 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -9,6 +9,16 @@
     bug: "260873529"
 }
 
+# Using a fixed read only flag because there are ClientTransaction scheduling before
+# WindowManagerService creation.
+flag {
+    namespace: "windowing_sdk"
+    name: "bundle_client_transaction_flag"
+    description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
+    bug: "260873529"
+    is_fixed_read_only: true
+}
+
 flag {
     namespace: "windowing_sdk"
     name: "activity_embedding_overlay_presentation_flag"
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 7c4252e..6b074a6 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -33,6 +33,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
 import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
@@ -131,6 +132,18 @@
     }
 
     /**
+     * Logs magnification that is assigned to the two finger triple tap shortcut. Calls this when
+     * triggering the magnification two finger triple tap shortcut.
+     */
+    public static void logMagnificationTwoFingerTripleTap(boolean enabled) {
+        FrameworkStatsLog.write(FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED,
+                MAGNIFICATION_COMPONENT_NAME.flattenToString(),
+                // jean update
+                ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP,
+                convertToLoggingServiceStatus(enabled));
+    }
+
+    /**
      * Logs accessibility feature name that is assigned to the long pressed accessibility button
      * shortcut. Calls this when clicking the long pressed accessibility button shortcut.
      *
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 68e2b48..ea4fc39 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -388,4 +388,14 @@
     oneway void notifyActivityEventChanged(
             in IBinder activityToken,
             int type);
+
+    /**
+      * Sets the sandboxed detection training data egress op to provided op-mode.
+      * Caller must be the active assistant and a preinstalled assistant.
+      *
+      * @param opMode app-op mode to set training data egress op to.
+      *
+      * @return whether was able to successfully set training data egress op.
+      */
+      boolean setSandboxedDetectionTrainingDataOp(int opMode);
 }
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 755113b..0dbdb36 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -690,18 +690,6 @@
         }
     }
 
-    public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
-        ensureNotDead();
-        mCommonProcess.addCachedKill(1, pss, pss, pss);
-        if (!mCommonProcess.mMultiPackage) {
-            return;
-        }
-
-        for (int ip=pkgList.size()-1; ip>=0; ip--) {
-            pullFixedProc(pkgList, ip).addCachedKill(1, pss, pss, pss);
-        }
-    }
-
     public ProcessState pullFixedProc(String pkgName) {
         if (mMultiPackage) {
             // The array map is still pointing to a common process state
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index df6c153..7be27be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -54,22 +54,6 @@
      */
     public static final class NotificationFlags {
 
-        /**
-         * FOR DEVELOPMENT / TESTING ONLY!!!
-         * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
-         * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
-         */
-        public static final Flag FSI_FORCE_DEMOTE =
-                devFlag("persist.sysui.notification.fsi_force_demote");
-
-        /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
-        public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
-                releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
-
-        /** Gating the redaction of OTP notifications on the lockscreen */
-        public static final Flag OTP_REDACTION =
-                devFlag("persist.sysui.notification.otp_redaction");
-
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index d503904..37aaa72 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -16,9 +16,14 @@
 
 package com.android.internal.display;
 
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
@@ -54,8 +59,7 @@
     private static final int MSG_RUN_UPDATE = 1;
 
     // The tolerance within which we consider brightness values approximately equal to eachother.
-    // This value is approximately 1/3 of the smallest possible brightness value.
-    public static final float EPSILON = 0.001f;
+    public static final float EPSILON = 0.0001f;
 
     private static int sBrightnessUpdateCount = 1;
 
@@ -70,16 +74,22 @@
     private BrightnessUpdate mCurrentUpdate;
     private BrightnessUpdate mPendingUpdate;
 
-    public BrightnessSynchronizer(Context context) {
-        this(context, Looper.getMainLooper(), SystemClock::uptimeMillis);
+    // Feature flag that will eventually be removed
+    private final boolean mIntRangeUserPerceptionEnabled;
+
+    public BrightnessSynchronizer(Context context, boolean intRangeUserPerceptionEnabled) {
+        this(context, Looper.getMainLooper(), SystemClock::uptimeMillis,
+                intRangeUserPerceptionEnabled);
     }
 
     @VisibleForTesting
-    public BrightnessSynchronizer(Context context, Looper looper, Clock clock) {
+    public BrightnessSynchronizer(Context context, Looper looper, Clock clock,
+            boolean intRangeUserPerceptionEnabled) {
         mContext = context;
         mClock = clock;
         mBrightnessSyncObserver = new BrightnessSyncObserver();
         mHandler = new BrightnessSynchronizerHandler(looper);
+        mIntRangeUserPerceptionEnabled = intRangeUserPerceptionEnabled;
     }
 
     /**
@@ -128,6 +138,7 @@
         pw.println("  mLatestFloatBrightness=" + mLatestFloatBrightness);
         pw.println("  mCurrentUpdate=" + mCurrentUpdate);
         pw.println("  mPendingUpdate=" + mPendingUpdate);
+        pw.println("  mIntRangeUserPerceptionEnabled=" + mIntRangeUserPerceptionEnabled);
     }
 
     /**
@@ -284,6 +295,78 @@
     }
 
     /**
+     * Converts between the int brightness setting and the float brightness system. The int
+     * brightness setting is between 0-255 and matches the brightness slider - e.g. 128 is 50% on
+     * the slider. Accounts for special values such as OFF and invalid values. Accounts for
+     * brightness limits; the maximum value here represents the max value allowed on the slider.
+     */
+    @RequiresPermission(CONTROL_DISPLAY_BRIGHTNESS)
+    public static float brightnessIntSettingToFloat(Context context, int brightnessInt) {
+        if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
+            return PowerManager.BRIGHTNESS_OFF_FLOAT;
+        } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        } else {
+            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+            final float maxInt = PowerManager.BRIGHTNESS_ON;
+
+            // Normalize to the range [0, 1]
+            float userPerceptionBrightness = MathUtils.norm(minInt, maxInt, brightnessInt);
+
+            // Convert from user-perception to linear scale
+            float linearBrightness = BrightnessUtils.convertGammaToLinear(userPerceptionBrightness);
+
+            // Interpolate to the range [0, currentlyAllowedMax]
+            final Display display = context.getDisplay();
+            if (display == null) {
+                return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            }
+            final BrightnessInfo info = display.getBrightnessInfo();
+            if (info == null) {
+                return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            }
+            return MathUtils.lerp(info.brightnessMinimum, info.brightnessMaximum, linearBrightness);
+        }
+    }
+
+    /**
+     * Translates specified value from the float brightness system to the setting int brightness
+     * system. The value returned is between 0-255 and matches the brightness slider - e.g. 128 is
+     * 50% on the slider. Accounts for special values such as OFF and invalid values. Accounts for
+     * brightness limits; the maximum value here represents the max value currently allowed on
+     * the slider.
+     */
+    @RequiresPermission(CONTROL_DISPLAY_BRIGHTNESS)
+    public static int brightnessFloatToIntSetting(Context context, float brightnessFloat) {
+        if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
+            return PowerManager.BRIGHTNESS_OFF;
+        } else if (Float.isNaN(brightnessFloat)) {
+            return PowerManager.BRIGHTNESS_INVALID;
+        } else {
+            // Normalize to the range [0, 1]
+            final Display display = context.getDisplay();
+            if (display == null) {
+                return PowerManager.BRIGHTNESS_INVALID;
+            }
+            final BrightnessInfo info = display.getBrightnessInfo();
+            if (info == null) {
+                return PowerManager.BRIGHTNESS_INVALID;
+            }
+            float linearBrightness =
+                    MathUtils.norm(info.brightnessMinimum, info.brightnessMaximum, brightnessFloat);
+
+            // Convert from linear to user-perception scale
+            float userPerceptionBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
+
+            // Interpolate to the range [0, 255]
+            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+            final float maxInt = PowerManager.BRIGHTNESS_ON;
+            float intBrightness = MathUtils.lerp(minInt, maxInt, userPerceptionBrightness);
+            return Math.round(intBrightness);
+        }
+    }
+
+    /**
      * Encapsulates a brightness change event and contains logic for synchronizing the appropriate
      * settings for the specified brightness change.
      */
@@ -417,18 +500,28 @@
             return mUpdatedTypes != 0x0;
         }
 
+        @SuppressLint("AndroidFrameworkRequiresPermission")
         private int getBrightnessAsInt() {
             if (mSourceType == TYPE_INT) {
                 return (int) mBrightness;
             }
-            return brightnessFloatToInt(mBrightness);
+            if (mIntRangeUserPerceptionEnabled) {
+                return brightnessFloatToIntSetting(mContext, mBrightness);
+            } else {
+                return brightnessFloatToInt(mBrightness);
+            }
         }
 
+        @SuppressLint("AndroidFrameworkRequiresPermission")
         private float getBrightnessAsFloat() {
             if (mSourceType == TYPE_FLOAT) {
                 return mBrightness;
             }
-            return brightnessIntToFloat((int) mBrightness);
+            if (mIntRangeUserPerceptionEnabled) {
+                return brightnessIntSettingToFloat(mContext, (int) mBrightness);
+            } else {
+                return brightnessIntToFloat((int) mBrightness);
+            }
         }
 
         private String toStringLabel(int type, float brightness) {
diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/core/java/com/android/internal/display/BrightnessUtils.java
similarity index 98%
rename from services/core/java/com/android/server/display/BrightnessUtils.java
rename to core/java/com/android/internal/display/BrightnessUtils.java
index 84fa0cc..6743bab 100644
--- a/services/core/java/com/android/server/display/BrightnessUtils.java
+++ b/core/java/com/android/internal/display/BrightnessUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.display;
+package com.android.internal.display;
 
 import android.util.MathUtils;
 
diff --git a/core/java/com/android/internal/jank/DisplayRefreshRate.java b/core/java/com/android/internal/jank/DisplayRefreshRate.java
new file mode 100644
index 0000000..354c413
--- /dev/null
+++ b/core/java/com/android/internal/jank/DisplayRefreshRate.java
@@ -0,0 +1,86 @@
+/*
+ * 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.jank;
+
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__UNKNOWN_REFRESH_RATE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__VARIABLE_REFRESH_RATE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_30_HZ;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_60_HZ;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_90_HZ;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_120_HZ;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_240_HZ;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Display refresh rate related functionality.
+ * @hide
+ */
+public class DisplayRefreshRate {
+    public static final int UNKNOWN_REFRESH_RATE =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__UNKNOWN_REFRESH_RATE;
+    public static final int VARIABLE_REFRESH_RATE =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__VARIABLE_REFRESH_RATE;
+    public static final int REFRESH_RATE_30_HZ =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_30_HZ;
+    public static final int REFRESH_RATE_60_HZ =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_60_HZ;
+    public static final int REFRESH_RATE_90_HZ =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_90_HZ;
+    public static final int REFRESH_RATE_120_HZ =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_120_HZ;
+    public static final int REFRESH_RATE_240_HZ =
+            UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_REFRESH_RATE__RR_240_HZ;
+
+    /** @hide */
+    @IntDef({
+        UNKNOWN_REFRESH_RATE,
+        VARIABLE_REFRESH_RATE,
+        REFRESH_RATE_30_HZ,
+        REFRESH_RATE_60_HZ,
+        REFRESH_RATE_90_HZ,
+        REFRESH_RATE_120_HZ,
+        REFRESH_RATE_240_HZ,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RefreshRate {
+    }
+
+    private DisplayRefreshRate() {
+    }
+
+    /**
+     * Computes the display refresh rate based off the frame interval.
+     */
+    @RefreshRate
+    public static int getRefreshRate(long frameIntervalNs) {
+        long rate = Math.round(1e9 / frameIntervalNs);
+        if (rate < 50) {
+            return REFRESH_RATE_30_HZ;
+        } else if (rate < 80) {
+            return REFRESH_RATE_60_HZ;
+        } else if (rate < 110) {
+            return REFRESH_RATE_90_HZ;
+        } else if (rate < 180) {
+            return REFRESH_RATE_120_HZ;
+        } else {
+            return REFRESH_RATE_240_HZ;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 506f19f..c83452d 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -24,6 +24,8 @@
 import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
 import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
 
+import static com.android.internal.jank.DisplayRefreshRate.UNKNOWN_REFRESH_RATE;
+import static com.android.internal.jank.DisplayRefreshRate.VARIABLE_REFRESH_RATE;
 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
 import static com.android.internal.jank.InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT;
@@ -49,6 +51,7 @@
 import android.view.WindowCallbacks;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.DisplayRefreshRate.RefreshRate;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.jank.InteractionJankMonitor.Session;
 import com.android.internal.util.FrameworkStatsLog;
@@ -132,26 +135,30 @@
         boolean hwuiCallbackFired;
         boolean surfaceControlCallbackFired;
         @JankType int jankType;
+        @RefreshRate int refreshRate;
 
         static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
                 boolean isFirstFrame) {
-            return new JankInfo(frameVsyncId, true, false, JANK_NONE, totalDurationNanos,
-                    isFirstFrame);
+            return new JankInfo(frameVsyncId, true, false, JANK_NONE, UNKNOWN_REFRESH_RATE,
+                    totalDurationNanos, isFirstFrame);
         }
 
         static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
-                @JankType int jankType) {
-            return new JankInfo(frameVsyncId, false, true, jankType, 0, false /* isFirstFrame */);
+                @JankType int jankType, @RefreshRate int refreshRate) {
+            return new JankInfo(
+                    frameVsyncId, false, true, jankType, refreshRate, 0, false /* isFirstFrame */);
         }
 
         private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
                 boolean surfaceControlCallbackFired, @JankType int jankType,
+                @RefreshRate int refreshRate,
                 long totalDurationNanos, boolean isFirstFrame) {
             this.frameVsyncId = frameVsyncId;
             this.hwuiCallbackFired = hwuiCallbackFired;
             this.surfaceControlCallbackFired = surfaceControlCallbackFired;
-            this.totalDurationNanos = totalDurationNanos;
             this.jankType = jankType;
+            this.refreshRate = refreshRate;
+            this.totalDurationNanos = totalDurationNanos;
             this.isFirstFrame = isFirstFrame;
         }
 
@@ -468,14 +475,16 @@
                 if (!isInRange(jankStat.frameVsyncId)) {
                     continue;
                 }
+                int refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
                 JankInfo info = findJankInfo(jankStat.frameVsyncId);
                 if (info != null) {
                     info.surfaceControlCallbackFired = true;
                     info.jankType = jankStat.jankType;
+                    info.refreshRate = refreshRate;
                 } else {
                     mJankInfos.put((int) jankStat.frameVsyncId,
                             JankInfo.createFromSurfaceControlCallback(
-                                    jankStat.frameVsyncId, jankStat.jankType));
+                                    jankStat.frameVsyncId, jankStat.jankType, refreshRate));
                 }
             }
             processJankInfos();
@@ -592,6 +601,7 @@
         int missedSfFramesCount = 0;
         int maxSuccessiveMissedFramesCount = 0;
         int successiveMissedFramesCount = 0;
+        @RefreshRate int refreshRate = UNKNOWN_REFRESH_RATE;
 
         for (int i = 0; i < mJankInfos.size(); i++) {
             JankInfo info = mJankInfos.valueAt(i);
@@ -627,6 +637,10 @@
                             maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
                     successiveMissedFramesCount = 0;
                 }
+                if (info.refreshRate != UNKNOWN_REFRESH_RATE && info.refreshRate != refreshRate) {
+                    refreshRate = (refreshRate == UNKNOWN_REFRESH_RATE)
+                            ? info.refreshRate : VARIABLE_REFRESH_RATE;
+                }
                 // TODO (b/174755489): Early latch currently gets fired way too often, so we have
                 // to ignore it for now.
                 if (!mSurfaceOnly && !info.hwuiCallbackFired) {
@@ -669,6 +683,7 @@
             mStatsLog.write(
                     FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
                     mDisplayId,
+                    refreshRate,
                     mSession.getStatsdInteractionType(),
                     totalFramesCount,
                     missedFramesCount,
@@ -866,10 +881,10 @@
         }
 
         /** {@see FrameworkStatsLog#write) */
-        public void write(int code, int displayId,
+        public void write(int code, int displayId, @RefreshRate int refreshRate,
                 int arg1, long arg2, long arg3, long arg4, long arg5, long arg6, long arg7) {
             FrameworkStatsLog.write(code, arg1, arg2, arg3, arg4, arg5, arg6, arg7,
-                    mDisplayResolutionTracker.getResolution(displayId));
+                    mDisplayResolutionTracker.getResolution(displayId), refreshRate);
         }
     }
 
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 0947ec1..f62094d 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -618,4 +618,14 @@
     public int describeContents() {
         return 0;
     }
+
+    @Override
+    public VpnProfile clone() {
+        try {
+            return (VpnProfile) super.clone();
+        } catch (CloneNotSupportedException e) {
+            Log.wtf(TAG, e);
+            return null;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index 6f114e3..661628a 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -30,6 +30,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -49,6 +50,8 @@
     private final Clock mClock;
     private long mTimeshift;
 
+    public static final long UNDEFINED = -1;
+
     public MonotonicClock(File file) {
         mFile = new AtomicFile(file);
         mClock = Clock.SYSTEM_CLOCK;
@@ -98,14 +101,11 @@
             return;
         }
 
-        mFile.write(out -> {
-            try {
-                writeXml(out, Xml.newBinarySerializer());
-                out.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
-            }
-        });
+        try (FileOutputStream out = mFile.startWrite()) {
+            writeXml(out, Xml.newBinarySerializer());
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 70514c3..01c91ba 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -337,6 +337,12 @@
 
     @UnsupportedAppUsage
     public void update() {
+        synchronized (this) {
+            updateLocked();
+        }
+    }
+
+    private void updateLocked() {
         if (DEBUG) Slog.v(TAG, "Update: " + this);
 
         final long nowUptime = SystemClock.uptimeMillis();
diff --git a/core/java/com/android/internal/pm/OWNERS b/core/java/com/android/internal/pm/OWNERS
new file mode 100644
index 0000000..6ef34e2
--- /dev/null
+++ b/core/java/com/android/internal/pm/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36137
+
+file:/PACKAGE_MANAGER_OWNERS
+
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageHidden.java
similarity index 86%
rename from services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
rename to core/java/com/android/internal/pm/parsing/pkg/AndroidPackageHidden.java
index 1fafdf9..0d7b433 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageHidden.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing.pkg;
+package com.android.internal.pm.parsing.pkg;
 
 import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
@@ -22,16 +22,18 @@
 
 /**
  * Methods that normal consumers should not have access to. This usually means the field is stateful
- * or deprecated and should be access through {@link AndroidPackageUtils} or a system manager
- * class.
+ * or deprecated and should be access through
+ * {@link com.android.server.pm.parsing.pkg.AndroidPackageUtils} or a system manager class.
  * <p>
  * This is a separate interface, not implemented by the base {@link AndroidPackage} because Java
  * doesn't support non-public interface methods. The class must be cast to this interface.
  * <p>
  * Because they exist in different packages, some methods are duplicated from
  * android.content.pm.parsing.ParsingPackageHidden.
+ * @hide
  */
-interface AndroidPackageHidden {
+// TODO: remove public after moved PackageImpl and AndroidPackageUtils
+public interface AndroidPackageHidden {
 
     /**
      * @see ApplicationInfo#primaryCpuAbi
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageInternal.java
similarity index 96%
rename from services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
rename to core/java/com/android/internal/pm/parsing/pkg/AndroidPackageInternal.java
index 9eca7d6..6f8e658 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing.pkg;
+package com.android.internal.pm.parsing.pkg;
 
 import android.annotation.NonNull;
 
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
similarity index 97%
rename from services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
rename to core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index aeaff6d..7ef0b48 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing.pkg;
+package com.android.internal.pm.parsing.pkg;
 
 import android.content.pm.SigningDetails;
 
@@ -73,6 +73,8 @@
 
     ParsedPackage setApex(boolean isApex);
 
+    ParsedPackage setUpdatableSystem(boolean value);
+
     ParsedPackage markNotActivitiesAsNotExportedIfSingleUser();
 
     ParsedPackage setOdm(boolean odm);
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java b/core/java/com/android/internal/pm/pkg/AndroidPackageSplitImpl.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
rename to core/java/com/android/internal/pm/pkg/AndroidPackageSplitImpl.java
index c0f2c25..3c564e9 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
+++ b/core/java/com/android/internal/pm/pkg/AndroidPackageSplitImpl.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg;
+package com.android.internal.pm.pkg;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 
+import com.android.server.pm.pkg.AndroidPackageSplit;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -94,8 +96,8 @@
         if (this == o) return true;
         if (!(o instanceof AndroidPackageSplitImpl)) return false;
         AndroidPackageSplitImpl that = (AndroidPackageSplitImpl) o;
-        var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags && Objects.equals(
-                mName, that.mName) && Objects.equals(mPath, that.mPath)
+        var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags
+                && Objects.equals(mName, that.mName) && Objects.equals(mPath, that.mPath)
                 && Objects.equals(mClassLoaderName, that.mClassLoaderName);
 
         if (!fieldsEqual) return false;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
similarity index 98%
rename from services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
rename to core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 408a531..4ed361f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.parsing;
+package com.android.internal.pm.pkg.parsing;
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
@@ -32,6 +32,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -43,7 +44,6 @@
 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.server.pm.parsing.pkg.ParsedPackage;
 
 import java.security.PublicKey;
 import java.util.List;
@@ -345,6 +345,8 @@
 
     ParsingPackage setStaticSharedLibraryVersion(long staticSharedLibraryVersion);
 
+    ParsingPackage setUpdatableSystem(boolean value);
+
     ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
 
     ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageHidden.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java
rename to core/java/com/android/internal/pm/pkg/parsing/ParsingPackageHidden.java
index 9c1c9ac..5758fd7 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageHidden.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.parsing;
+package com.android.internal.pm.pkg.parsing;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 6c17e9e..dd310dc 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -175,6 +175,14 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private static final long NAV_BAR_COLOR_DEFAULT_TRANSPARENT = 232195501L;
 
+    /**
+     * Make app go edge-to-edge by default if the target SDK is
+     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long EDGE_TO_EDGE_BY_DEFAULT = 309578419;
+
     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
             (1 << FEATURE_CUSTOM_TITLE) |
             (1 << FEATURE_CONTENT_TRANSITIONS) |
@@ -387,7 +395,9 @@
         mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
         mDefaultEdgeToEdge =
-                context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION;
+                context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION
+                        || (CompatChanges.isChangeEnabled(EDGE_TO_EDGE_BY_DEFAULT)
+                                && Flags.edgeToEdgeByDefault());
         if (mDefaultEdgeToEdge) {
             mDecorFitsSystemWindows = false;
         }
@@ -2558,11 +2568,11 @@
 
             mNavigationBarColor =
                     navBarColor == navBarDefaultColor
+                            && !mDefaultEdgeToEdge
                             && !(CompatChanges.isChangeEnabled(NAV_BAR_COLOR_DEFAULT_TRANSPARENT)
                                     && Flags.navBarTransparentByDefault())
                             && !context.getResources().getBoolean(
                                     R.bool.config_navBarDefaultTransparent)
-                            && !mDefaultEdgeToEdge
                     ? navBarCompatibleColor
                     : navBarColor;
 
@@ -3895,6 +3905,9 @@
 
     @Override
     public void setStatusBarColor(int color) {
+        if (mStatusBarColor == color && mForcedStatusBarColor) {
+            return;
+        }
         mStatusBarColor = color;
         mForcedStatusBarColor = true;
         if (mDecor != null) {
@@ -3913,6 +3926,9 @@
 
     @Override
     public void setNavigationBarColor(int color) {
+        if (mNavigationBarColor == color && mForcedNavigationBarColor) {
+            return;
+        }
         mNavigationBarColor = color;
         mForcedNavigationBarColor = true;
         if (mDecor != null) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3977666..fc60f06 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -90,7 +90,7 @@
     void onNotificationSettingsViewed(String key);
     void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
     void onBubbleMetadataFlagChanged(String key, int flags);
-    void hideCurrentInputMethodForBubbles();
+    void hideCurrentInputMethodForBubbles(int displayId);
     void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
     oneway void clearInlineReplyUriPermissions(String key);
     void onNotificationFeedbackReceived(String key, in Bundle feedback);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index dadeb2b..aab2242 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -60,7 +60,8 @@
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyCallForwardingChanged(boolean cfi);
     void notifyCallForwardingChangedForSubscriber(in int subId, boolean cfi);
-    void notifyDataActivityForSubscriber(int phoneId, int subId, int state);
+    void notifyDataActivityForSubscriber(int subId, int state);
+    void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state);
     void notifyDataConnectionForSubscriber(
             int phoneId, int subId, in PreciseDataConnectionState preciseState);
     // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
diff --git a/core/java/com/android/server/pm/OWNERS b/core/java/com/android/server/pm/OWNERS
new file mode 100644
index 0000000..6ef34e2
--- /dev/null
+++ b/core/java/com/android/server/pm/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36137
+
+file:/PACKAGE_MANAGER_OWNERS
+
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
similarity index 98%
rename from services/core/java/com/android/server/pm/pkg/AndroidPackage.java
rename to core/java/com/android/server/pm/pkg/AndroidPackage.java
index 4d4efac..4e4f26c 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -58,7 +58,6 @@
 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.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.security.PublicKey;
 import java.util.List;
@@ -691,7 +690,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under {@link
-     * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
+     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      *
      * @see R.styleable#AndroidManifestOriginalPackage_name
      * @hide
@@ -796,7 +795,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link
-     * ParsingPackageUtils#TAG_KEY_SETS}.
+     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}.
      *
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
@@ -905,7 +904,7 @@
      * For system use to migrate from an old package name to a new one, moving over data if
      * available.
      *
-     * @see R.styleable#AndroidManifestOriginalPackage}
+     * @see R.styleable#AndroidManifestOriginalPackage
      * @hide
      */
     @NonNull
@@ -1267,7 +1266,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link
-     * ParsingPackageUtils#TAG_KEY_SETS}.
+     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}.
      *
      * @see R.styleable#AndroidManifestUpgradeKeySet
      * @hide
@@ -1393,6 +1392,13 @@
     /** @hide */
     boolean isApex();
 
+
+    /**
+     * @see R.styleable#AndroidManifestApplication_updatableSystem
+     * @hide
+     */
+    boolean isUpdatableSystem();
+
     /**
      * @see ApplicationInfo#enabled
      * @see R.styleable#AndroidManifestApplication_enabled
@@ -1410,6 +1416,7 @@
      * @see ApplicationInfo#FLAG_IS_GAME
      * @see R.styleable#AndroidManifestApplication_isGame
      * @hide
+     * @deprecated
      */
     @Deprecated
     boolean isGame();
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java b/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
similarity index 100%
rename from services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
rename to core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 440a332..f365dbb 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -462,9 +462,4 @@
             ],
         },
     },
-
-    // Workaround Clang LTO crash.
-    lto: {
-        never: true,
-    },
 }
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index aebe7ea..95bf49f 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -16,16 +16,15 @@
 
 #define LOG_TAG "PerfHint-jni"
 
-#include <android/performance_hint.h>
+#include "jni.h"
+
 #include <dlfcn.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <utils/Log.h>
-
 #include <vector>
 
 #include "core_jni_helpers.h"
-#include "jni.h"
 
 namespace android {
 
@@ -45,11 +44,6 @@
 typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
 typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
 typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
-typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*);
-
-typedef AWorkDuration* (*AWD_create)();
-typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t);
-typedef void (*AWD_release)(AWorkDuration*);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
@@ -62,14 +56,6 @@
 APH_setThreads gAPH_setThreadsFn = nullptr;
 APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
 APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
-APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr;
-
-AWD_create gAWD_createFn = nullptr;
-AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr;
-AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr;
-AWD_release gAWD_releaseFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -126,46 +112,9 @@
             (APH_setPreferPowerEfficiency)dlsym(handle_,
                                                 "APerformanceHint_setPreferPowerEfficiency");
     LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
-                        "Failed to find required symbol "
+                        "Failed to find required symbol"
                         "APerformanceHint_setPreferPowerEfficiency!");
 
-    gAPH_reportActualWorkDuration2Fn =
-            (APH_reportActualWorkDuration2)dlsym(handle_,
-                                                 "APerformanceHint_reportActualWorkDuration2");
-    LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr,
-                        "Failed to find required symbol "
-                        "APerformanceHint_reportActualWorkDuration2!");
-
-    gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create");
-    LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr,
-                        "Failed to find required symbol AWorkDuration_create!");
-
-    gAWD_setWorkPeriodStartTimestampNanosFn =
-            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos");
-    LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr,
-                        "Failed to find required symbol "
-                        "AWorkDuration_setWorkPeriodStartTimestampNanos!");
-
-    gAWD_setActualTotalDurationNanosFn =
-            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos");
-    LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr,
-                        "Failed to find required symbol "
-                        "AWorkDuration_setActualTotalDurationNanos!");
-
-    gAWD_setActualCpuDurationNanosFn =
-            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos");
-    LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr,
-                        "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!");
-
-    gAWD_setActualGpuDurationNanosFn =
-            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos");
-    LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr,
-                        "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!");
-
-    gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release");
-    LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr,
-                        "Failed to find required symbol AWorkDuration_release!");
-
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -289,25 +238,6 @@
                                     enabled);
 }
 
-static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
-                                            jlong workPeriodStartTimestampNanos,
-                                            jlong actualTotalDurationNanos,
-                                            jlong actualCpuDurationNanos,
-                                            jlong actualGpuDurationNanos) {
-    ensureAPerformanceHintBindingInitialized();
-
-    AWorkDuration* workDuration = gAWD_createFn();
-    gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos);
-    gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos);
-    gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos);
-    gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos);
-
-    gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
-                                     workDuration);
-
-    gAWD_releaseFn(workDuration);
-}
-
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -319,7 +249,6 @@
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
         {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
-        {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 18c60a7..91dfc60 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1245,7 +1245,7 @@
 
 void android_os_Process_removeAllProcessGroups(JNIEnv* env, jobject clazz)
 {
-    return removeAllProcessGroups();
+    return removeAllEmptyProcessGroups();
 }
 
 static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 262f5e8..239c626 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -81,7 +81,8 @@
                                           deviceInfo.getId(), deviceInfo.getGeneration(),
                                           deviceInfo.getControllerNumber(), nameObj.get(),
                                           static_cast<int32_t>(ident.vendor),
-                                          static_cast<int32_t>(ident.product), descriptorObj.get(),
+                                          static_cast<int32_t>(ident.product),
+                                          static_cast<int32_t>(ident.bus), descriptorObj.get(),
                                           deviceInfo.isExternal(), deviceInfo.getSources(),
                                           deviceInfo.getKeyboardType(), kcmObj.get(),
                                           keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(),
@@ -111,7 +112,7 @@
     gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
 
     gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
-                                                  "(IIILjava/lang/String;IILjava/lang/"
+                                                  "(IIILjava/lang/String;IIILjava/lang/"
                                                   "String;ZIILandroid/view/KeyCharacterMap;Ljava/"
                                                   "lang/String;Ljava/lang/String;ZZZZZIII)V");
 
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5c1d91f..5b68e8e 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -184,11 +184,11 @@
 void NativeInputEventReceiver::setFdEvents(int events) {
     if (mFdEvents != events) {
         mFdEvents = events;
-        int fd = mInputConsumer.getChannel()->getFd();
+        auto&& fd = mInputConsumer.getChannel()->getFd();
         if (events) {
-            mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
+            mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
         } else {
-            mMessageQueue->getLooper()->removeFd(fd);
+            mMessageQueue->getLooper()->removeFd(fd.get());
         }
     }
 }
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 833952d..6bdf821 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
 }
 
 status_t NativeInputEventSender::initialize() {
-    int receiveFd = mInputPublisher.getChannel()->getFd();
-    mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
+    auto&& receiveFd = mInputPublisher.getChannel()->getFd();
+    mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
     return OK;
 }
 
@@ -112,7 +112,7 @@
         LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
     }
 
-    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
 }
 
 status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a800e6e..db42246 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1951,8 +1951,9 @@
         jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
                 gJankDataClassInfo.clazz, nullptr);
         for (size_t i = 0; i < jankData.size(); i++) {
-            jobject jJankData = env->NewObject(gJankDataClassInfo.clazz,
-                    gJankDataClassInfo.ctor, jankData[i].frameVsyncId, jankData[i].jankType);
+            jobject jJankData = env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
+                                               jankData[i].frameVsyncId, jankData[i].jankType,
+                                               jankData[i].frameIntervalNs);
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
             env->DeleteLocalRef(jJankData);
         }
@@ -2523,8 +2524,7 @@
     jclass jankDataClazz =
                 FindClassOrDie(env, "android/view/SurfaceControl$JankData");
     gJankDataClassInfo.clazz = MakeGlobalRefOrDie(env, jankDataClazz);
-    gJankDataClassInfo.ctor =
-            GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JI)V");
+    gJankDataClassInfo.ctor = GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JIJ)V");
     jclass onJankDataListenerClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$OnJankDataListener");
     gJankDataListenerClassInfo.clazz = MakeGlobalRefOrDie(env, onJankDataListenerClazz);
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index bdf7eaa..6e903b3 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -53,7 +53,6 @@
     jfieldID displayToken;
     jfieldID width;
     jfieldID height;
-    jfieldID useIdentityTransform;
 } gDisplayCaptureArgsClassInfo;
 
 static struct {
@@ -194,9 +193,6 @@
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
     captureArgs.height =
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
-    captureArgs.useIdentityTransform =
-            env->GetBooleanField(displayCaptureArgsObject,
-                                 gDisplayCaptureArgsClassInfo.useIdentityTransform);
     return captureArgs;
 }
 
@@ -325,8 +321,6 @@
             GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I");
     gDisplayCaptureArgsClassInfo.height =
             GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I");
-    gDisplayCaptureArgsClassInfo.useIdentityTransform =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z");
 
     jclass layerCaptureArgsClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$LayerCaptureArgs");
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index 2b74220..11b367b 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -92,8 +92,24 @@
     message UidBatteryConsumer {
         optional int32 uid = 1;
         optional BatteryConsumerData battery_consumer_data = 2;
-        optional int64 time_in_foreground_millis = 3;
-        optional int64 time_in_background_millis = 4;
+        // DEPRECATED Use time_in_state instead.
+        optional int64 time_in_foreground_millis = 3 [deprecated = true];
+        // DEPRECATED Use time_in_state instead.
+        optional int64 time_in_background_millis = 4 [deprecated = true];
+
+        message TimeInState {
+            enum ProcessState {
+                UNSPECIFIED = 0;
+                FOREGROUND = 1;
+                BACKGROUND = 2;
+                FOREGROUND_SERVICE = 3;
+            }
+
+            optional ProcessState process_state = 1;
+            optional int64 time_in_state_millis = 2;
+        }
+
+        repeated TimeInState time_in_state = 5;
     }
     repeated UidBatteryConsumer uid_battery_consumers = 5;
 
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
index d5cf60d..c242c9c 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -110,6 +110,7 @@
   optional int32 task_root_package_token = 11;
   optional int32 task_root_class_token = 12;
   optional int32 locus_id_token = 13;
+  optional ObfuscatedUserInteractionExtrasProto interaction_extras = 14;
 }
 
 /**
@@ -129,6 +130,7 @@
   optional string task_root_package = 11;
   optional string task_root_class = 12;
   optional string locus_id = 13 [(.android.privacy).dest = DEST_EXPLICIT];
+  optional bytes extras = 14;
 }
 
 /**
@@ -145,3 +147,11 @@
   // Stores the mappings for every package
   repeated PackagesMap packages_map = 2;
 }
+
+/**
+ * Store the relevant information from extra details for user interaction event.
+ */
+message ObfuscatedUserInteractionExtrasProto {
+  optional int32 category_token = 1;
+  optional int32 action_token = 2;
+}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 4e686b7..34c4045 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -152,6 +152,8 @@
             "simulated_device_launcher",
         ],
     },
+
+    generate_product_characteristics_rro: true,
 }
 
 java_genrule {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 76ae3e0..0021640 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -822,6 +822,7 @@
     <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
     <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
+    <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" />
     <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
@@ -2300,6 +2301,7 @@
     <!-- Allows system apps to call methods to register itself as a mDNS offload engine.
         <p>Not for use by third-party or privileged applications.
         @SystemApi
+        @FlaggedApi("com.android.net.flags.register_nsd_offload_engine")
         @hide This should only be used by system apps.
     -->
     <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
@@ -2372,7 +2374,7 @@
          them from running without explicit user action.
     -->
     <permission android:name="android.permission.QUARANTINE_APPS"
-        android:protectionLevel="internal|verifier" />
+        android:protectionLevel="signature|verifier" />
 
     <!-- Allows applications to discover and pair bluetooth devices.
          <p>Protection level: normal
@@ -7815,6 +7817,13 @@
     <permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an app to track all preparations for a complete factory reset.
+     <p>Protection level: signature|privileged
+     @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis")
+     @hide -->
+    <permission android:name="android.permission.PREPARE_FACTORY_RESET"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/OWNERS b/core/res/OWNERS
index f24c3f5..332ad2a 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -47,6 +47,10 @@
 # Wear
 per-file res/*-watch/* = file:/WEAR_OWNERS
 
+# Peformance
+per-file res/values/config.xml = file:/PERFORMANCE_OWNERS
+per-file res/values/symbols.xml = file:/PERFORMANCE_OWNERS
+
 # PowerProfile
 per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS
 per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 5037239..f5b82892 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Dit kan jou interaksies met \'n app of \'n hardewaresensor naspoor en namens jou met apps interaksie hê."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Laat toe"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Weier"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deïnstalleer"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"’n App verberg die toestemmingversoek en jou antwoord kan dus nie geverifieer word nie."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tik op \'n kenmerk om dit te begin gebruik:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Kies kenmerke om saam met die toeganklikheidknoppie te gebruik"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Kies kenmerke om saam met die volumesleutelkortpad te gebruik"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Naweek"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Geleentheid"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Slaap"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aan"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Af"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> demp sekere klanke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Daar is \'n interne probleem met jou toestel en dit sal dalk onstabiel wees totdat jy \'n fabriekterugstelling doen."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Daar is \'n interne probleem met jou toestel. Kontak jou vervaardiger vir besonderhede."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoon is geblokkeer"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan nie na skerm weerspieël nie"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik ’n ander kabel en probeer weer"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Jou toestel is te warm en kan nie na die skerm weerspieël totdat dit afgekoel het nie"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel steun dalk nie skerms nie"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Jou USB-C-kabel koppel dalk nie behoorlik aan skerms nie"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 9a8bb62..fbef5011 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ከመተግበሪያ ጋር ወይም የሃርድዌር ዳሳሽ ጋር እርስዎ ያልዎትን መስተጋብሮች ዱካ መከታተል እና በእርስዎ ምትክ ከመተግበሪያዎች ጋር መስተጋብር መፈጸም ይችላል።"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ፍቀድ"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ከልክል"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"አራግፍ"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"አንድ መተግበሪያ የፍቃድ ጥያቄውን እያደበዘዘ ነው ስለዚህ የእርስዎ ምላሽ ሊረጋገጥ አይችልም።"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"አንድ ባህሪን መጠቀም ለመጀመር መታ ያድርጉት፦"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"በተደራሽነት አዝራር የሚጠቀሙባቸው ባሕሪያት ይምረጡ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"በድምጽ ቁልፍ አቋራጭ የሚጠቀሙባቸው ባሕሪያት ይምረጡ"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"የሳምንት እረፍት ቀናት"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ክስተት"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"መተኛት"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"በርቷል"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ጠፍቷል"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> አንዳንድ ድምጾችን እየዘጋ ነው"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ፣ የፋብሪካ ውሂብ ዳግም እስኪያስጀምሩት ድረስ ላይረጋጋ ይችላል።"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ። ዝርዝሮችን ለማግኘት አምራችዎን ያነጋግሩ።"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"መሳሪያዎ በጣም ሞቃት ነው እና እስኪቀዘቅዝ ድረስ ማሳያውን ማንጸባረቅ አይችልም"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index f6d7c64..9ca2d199 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1694,7 +1694,7 @@
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"إزالة"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟\n\nقد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string>
     <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"هل تريد مواصلة الاستماع بصوت عالٍ؟\n\nكان مستوى صوت سمّاعة الرأس مرتفعًا لمدة أطول مما يُنصَح به، وقد يضر هذا بسمعك."</string>
-    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"تم رصد صوت مرتفع.\n\nكان مستوى صوت سمّاعة الرأس مرتفعًا لمدة أطول مما يُنصَح به، وقد يضر هذا بسمعك."</string>
+    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"تم رصد صوت مرتفع\n\nكان مستوى صوت سمّاعة الرأس مرتفعًا لمدة أطول مما يُنصَح به، وقد يضر هذا بسمعك."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"هل تريد استخدام اختصار \"سهولة الاستخدام\"؟"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"عند تفعيل الاختصار، يؤدي الضغط على زرّي التحكّم في مستوى الصوت معًا لمدة 3 ثوانٍ إلى تفعيل إحدى ميزات إمكانية الوصول."</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"هل تريد تفعيل الاختصار لميزات إمكانية الوصول؟"</string>
@@ -1714,6 +1714,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"قد يؤدي ذلك إلى السماح للميزة بتتبّع تفاعلاتك مع تطبيق أو جهاز استشعار والتفاعل مع التطبيقات نيابةً عنك."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"سماح"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"رفض"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"إلغاء التثبيت"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"تعذَّر التحقّق من ردّك بسبب حجب أحد التطبيقات طلب الحصول على الإذن."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"انقر على ميزة لبدء استخدامها:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"اختيار الميزات التي تريد استخدامها مع زر أدوات تمكين الوصول"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"اختيار الميزات التي تريد استخدامها مع اختصار مفتاح التحكّم في مستوى الصوت"</string>
@@ -1908,6 +1910,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"نهاية الأسبوع"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"حدث"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"النوم"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"مفعَّل"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"غير مفعَّل"</string>
     <string name="muted_by" msgid="91464083490094950">"يعمل <xliff:g id="THIRD_PARTY">%1$s</xliff:g> على كتم بعض الأصوات."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط على الإعدادات الأصلية."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"حدثت مشكلة داخلية في جهازك. يمكنك الاتصال بالمصنِّع للحصول على تفاصيل."</string>
@@ -2340,6 +2344,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"جهازك ساخن للغاية ولا يمكنه إجراء نسخ مطابق للمحتوى إلى الشاشة إلى أن تنخفض حرارته."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 517ae9a..7473f49 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ই আপুনি কোনো এপ্ বা হার্ডৱেৰ ছেন্সৰৰ সৈতে কৰা ভাব-বিনিময় আৰু আপোনাৰ হৈ অন্য কোনো লোকে এপৰ সৈতে কৰা ভাব-বিনিময় ট্ৰেক কৰিব পাৰে।"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"অনুমতি দিয়ক"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"অস্বীকাৰ কৰক"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"আনইনষ্টল কৰক"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"এটা এপে অনুমতিৰ অনুৰোধটো অস্পষ্ট কৰি আছে আৰু সেয়েহে আপোনাৰ সঁহাৰিটো সত্যাপন কৰিব নোৱাৰি।"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"কোনো এটা সুবিধা ব্যৱহাৰ কৰিবলৈ সেইটোত টিপক:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"সাধ্য-সুবিধা বুটামটোৰ জৰিয়তে ব্যৱহাৰ কৰিবলৈ সুবিধাসমূহ বাছনি কৰক"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ভলিউম কীৰ শ্বৰ্টকাটটোৰ জৰিয়তে ব্যৱহাৰ কৰিবলৈ সুবিধাসমূহ বাছনি কৰক"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"সপ্তাহ অন্ত"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"কার্যক্ৰম"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"নিদ্ৰাৰত"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"অন আছে"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"অফ আছে"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>এ কিছুমান ধ্বনি মিউট কৰি আছে"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে আৰু আপুনি ফেক্টৰী ডেটা ৰিছেট নকৰালৈকে ই সুস্থিৰভাৱে কাম নকৰিব পাৰে।"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে। সবিশেষ জানিবৰ বাবে আপোনাৰ ডিভাইচ নির্মাতাৰ সৈতে যোগাযোগ কৰক।"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"আপোনাৰ ডিভাইচটো অত্যধিক গৰম হৈছে আৰু এইটো ঠাণ্ডা নোহোৱালৈকে ডিছপ্লে’ত প্ৰতিবিম্বকৰণ কৰিব নোৱাৰি"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index c011555..b1002e2 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Tətbiq və sensorlarla əlaqələrinizi izləyib tətbiqlərə adınızdan əmrlər verə bilər."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"İcazə verin"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"İmtina edin"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Sistemdən silin"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Bir tətbiq icazə sorğusunu gizlətdiyi üçün cavabı yoxlamaq mümkün deyil."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Funksiyanı istifadə etmək üçün onun üzərinə toxunun:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Xüsusi imkanlar düyməsinin köməyilə işə salınacaq funksiyaları seçin"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Səs səviyyəsi düyməsinin qısayolu ilə istifadə edəcəyiniz funksiyaları seçin"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Həftə sonu"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tədbir"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Yuxu vaxtı"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktiv"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Deaktiv"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bəzi səsləri səssiz rejimə salır"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızın daxili problemi var və istehsalçı sıfırlanması olmayana qədər qeyri-stabil ola bilər."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Cihazınızın daxili problemi var. Əlavə məlumat üçün istehsalçı ilə əlaqə saxlayın."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Cihaz çox isinib və soyuyana qədər displeydə əks etdirmək olmur"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 48b5c02..196818a 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može da prati interakcije sa aplikacijom ili senzorom hardvera i koristi aplikacije umesto vas."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dozvoli"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija krije zahtev za dozvolu, pa odgovor ne može da se verifikuje."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite neku funkciju da biste počeli da je koristite:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti sa dugmetom Pristupačnost"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije za prečicu tasterom jačine zvuka"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvuke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Došlo je do internog problema u vezi sa uređajem i možda će biti nestabilan dok ne obavite resetovanje na fabrička podešavanja."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Došlo je do internog problema u vezi sa uređajem. Potražite detalje od proizvođača."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je previše zagrejan, pa ne može da se preslikava na ekran dok se ne ohladi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 12effa0..d7efa8a 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Гэта функцыя можа адсочваць вашы ўзаемадзеянні з праграмай ці датчыкам апаратнага забеспячэння і ўзаемадзейнічаць з праграмамі ад вашага імя."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дазволіць"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Адмовіць"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Выдаліць"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Праграма хавае запыт дазволу, таму ваш адказ немагчыма спраўдзіць."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Каб пачаць выкарыстоўваць функцыю, націсніце на яе:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Выберыце функцыі, якія будзеце выкарыстоўваць з кнопкай спецыяльных магчымасцей"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберыце функцыі для выкарыстання з клавішай гучнасці"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Выхадныя"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Падзея"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Рэжым сну"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Уключана"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Выключана"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> выключае некаторыя гукі"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"На вашай прыладзе ўзнікла ўнутраная праблема, і яна можа працаваць нестабільна, пакуль вы не зробіце скід да заводскіх налад."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"На вашай прыладзе ўзнікла ўнутраная праблема. Для атрымання дадатковай інфармацыі звярніцеся да вытворцы."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Прылада занадта моцна нагрэлася і таму не можа дубліраваць змесціва на дысплэі, пакуль не астыне"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d18e4bc..b9e1db4 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Услугата може да проследява взаимодействията ви с дадено приложение или хардуерен сензор, както и да взаимодейства с приложенията от ваше име."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Разреш."</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Отказ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Деинсталиране"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Отговорът ви не може да бъде потвърден, тъй като приложение прикрива заявката за разрешение."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Докоснете дадена функция, за да започнете да я използвате:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Избиране на функции, които да използвате с бутона за достъпност"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Избиране на функции, които да използвате с прекия път чрез бутона за силата на звука"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Събота и неделя"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Събитие"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Време за сън"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Вкл."</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Изкл."</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> заглушава някои звуци"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Възникна вътрешен проблем с устройството ви. То може да е нестабилно, докато не възстановите фабричните настройки."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Възникна вътрешен проблем с устройството ви. За подробности се свържете с производителя."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Устройството ви е твърде топло и няма да може да дублира на екрана, преди да се охлади"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 859f37d..669c99e 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"এটি কোনও একটি অ্যাপের সাথে অথবা হার্ডওয়্যার সেন্সরের সাথে আপনার ইন্টার‍্যাকশন ট্র্যাক করতে এবং আপনার হয়ে বিভিন্ন অ্যাপের সাথে ইন্টার‍্যাক্ট করতে পারে।"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"অনুমতি দিন"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"খারিজ করুন"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"কোনও ফিচার ব্যবহার করা শুরু করতে, সেটিতে ট্যাপ করুন:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"অ্যাক্সেসিবিলিটি বোতামের সাহায্যে আপনি যেসব ফিচার ব্যবহার করতে চান সেগুলি বেছে নিন"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ভলিউম কী শর্টকাটের সাহায্যে আপনি যেসব ফিচার ব্যবহার করতে চান সেগুলি বেছে নিন"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"সপ্তাহান্ত"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ইভেন্ট"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ঘুমানোর সময়"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"চালু আছে"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"বন্ধ আছে"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> কিছু সাউন্ডকে মিউট করে দিচ্ছে"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে, এবং আপনি যতক্ষণ না পর্যন্ত এটিকে ফ্যাক্টরি ডেটা রিসেট করছেন ততক্ষণ এটি ঠিকভাবে কাজ নাও করতে পারে৷"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে৷ বিস্তারিত জানার জন্য প্রস্তুতকারকের সাথে যোগাযোগ করুন৷"</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"আপনার ডিভাইস খুব গরম হয়ে আছে এবং সেটি ঠাণ্ডা না হওয়া পর্যন্ত ডিসপ্লে মিরর করা যাবে না"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 20f1d68..9dcd869 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može pratiti vaše interakcije s aplikacijom ili hardverskim senzorom te ostvariti interakciju s aplikacijama umjesto vas."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dozvoli"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija skriva zahtjev za odobrenje, pa se vaš odgovor ne može potvrditi."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite funkciju da je počnete koristiti:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti s dugmetom Pristupačnost"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije koje ćete koristiti pomoću prečice tipke za jačinu zvuka"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Postoji problem u vašem uređaju i može biti nestabilan dok ga ne vratite na fabričke postavke."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Postoji problem u vašem uređaju. Za više informacija obratite se proizvođaču."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je pregrijan i ne može preslikavati na ekran dok se ne ohladi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 18e5abe..66e76ac 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pot fer un seguiment de les teves interaccions amb una aplicació o un sensor de maquinari, i interaccionar amb aplicacions en nom teu."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permet"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denega"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstal·la"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una aplicació està ocultant la sol·licitud de permís, de manera que la teva resposta no es pot verificar"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toca una funció per començar a utilitzar-la:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Tria les funcions que vols utilitzar amb el botó d\'accessibilitat"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Tria les funcions que vols utilitzar amb la drecera per a tecles de volum"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Cap de setmana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Esdeveniment"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Mentre dormo"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activat"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivat"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> està silenciant alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"S\'ha produït un error intern al dispositiu i és possible que funcioni de manera inestable fins que restableixis les dades de fàbrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"S\'ha produït un error intern al dispositiu. Contacta amb el fabricant del dispositiu per obtenir més informació."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot projectar a la pantalla fins que es refredi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 29e00fa..1acb8d4 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1712,6 +1712,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Služba může sledovat vaše interakce s aplikací nebo hardwarovým senzorem a komunikovat s aplikacemi namísto vás."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Povolit"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zakázat"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Chcete-li některou funkci začít používat, klepněte na ni:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Vyberte funkce, které budete používat s tlačítkem přístupnosti"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Vyberte funkce, které budete používat se zkratkou tlačítka hlasitosti"</string>
@@ -1906,6 +1910,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Víkend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Událost"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spánek"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Zapnuto"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Vypnuto"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypíná určité zvuky"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"V zařízení došlo k internímu problému. Dokud neprovedete obnovení továrních dat, může být nestabilní."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"V zařízení došlo k internímu problému. Další informace vám sdělí výrobce."</string>
@@ -2338,6 +2344,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Zařízení je moc zahřáté, a dokud se nezchladí, nemůže zrcadlit displej"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 87703cd..bedd941 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1638,7 +1638,7 @@
     <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Cast skærm til enhed"</string>
     <string name="media_route_chooser_searching" msgid="6119673534251329535">"Søger efter enheder…"</string>
     <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Indstillinger"</string>
-    <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Afbryd forbindelsen"</string>
+    <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Afbryd forbindelse"</string>
     <string name="media_route_status_scanning" msgid="8045156315309594482">"Søger..."</string>
     <string name="media_route_status_connecting" msgid="5845597961412010540">"Opretter forbindelse..."</string>
     <string name="media_route_status_available" msgid="1477537663492007608">"Tilgængelig"</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore dine interaktioner med en app eller en hardwaresensor og interagere med apps på dine vegne."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillad"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Afvis"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Afinstaller"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app skjuler anmodningen om tilladelse, så dit svar kan ikke verificeres."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tryk på en funktion for at bruge den:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Vælg, hvilke funktioner du vil bruge med knappen til hjælpefunktioner"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Vælg de funktioner, du vil bruge via lydstyrkeknapperne"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Begivenhed"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sover"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Til"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Fra"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår nogle lyde fra"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Der er et internt problem med enheden, og den vil muligvis være ustabil, indtil du gendanner fabriksdataene."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Der er et internt problem med enheden. Kontakt producenten for at få yderligere oplysninger."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Din enhed er for varm og kan ikke spejle til skærmen, før den er kølet af"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f8eb0a0..2867da9 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Die Funktion kann deine Interaktionen mit einer App oder einem Hardwaresensor verfolgen und in deinem Namen mit Apps interagieren."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Zulassen"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Ablehnen"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstallieren"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Eine App verdeckt die Berechtigungsanfrage und deine Antwort kann deshalb nicht überprüft werden."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Zum Auswählen der gewünschten Funktion tippen:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Funktionen auswählen, die du mit der Schaltfläche \"Bedienungshilfen\" verwenden möchtest"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Funktionen für Verknüpfung mit Lautstärketaste auswählen"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Wochenende"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Termin"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Schlafen"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"An"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Aus"</string>
     <string name="muted_by" msgid="91464083490094950">"Einige Töne werden von <xliff:g id="THIRD_PARTY">%1$s</xliff:g> stummgeschaltet"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Es liegt ein internes Problem mit deinem Gerät vor. Möglicherweise verhält es sich instabil, bis du es auf die Werkseinstellungen zurücksetzt."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Es liegt ein internes Problem mit deinem Gerät vor. Bitte wende dich diesbezüglich an den Hersteller."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Dein Gerät ist zu heiß und kann den Bildschirm erst spiegeln, wenn es abgekühlt ist"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index af53ddf..b6b4da2 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Μπορεί να παρακολουθήσει τις αλληλεπιδράσεις σας με μια εφαρμογή ή έναν αισθητήρα εξοπλισμού και να αλληλεπιδράσει με εφαρμογές εκ μέρους σας."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ναι"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Όχι"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Απεγκατάσταση"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Μια εφαρμογή αποκρύπτει το αίτημα άδειας, με αποτέλεσμα να μην είναι δυνατή η επαλήθευση της απάντησής σας."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Πατήστε μια λειτουργία για να ξεκινήσετε να τη χρησιμοποιείτε:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με το κουμπί προσβασιμότητας."</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με τη συντόμευση κουμπιού έντασης ήχου"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Σαββατοκύριακο"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Συμβάν"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ύπνος"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ενεργός"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Ανενεργός"</string>
     <string name="muted_by" msgid="91464083490094950">"Το τρίτο μέρος <xliff:g id="THIRD_PARTY">%1$s</xliff:g> θέτει ορισμένους ήχους σε σίγαση"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας και ενδέχεται να είναι ασταθής μέχρι την επαναφορά των εργοστασιακών ρυθμίσεων."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας. Επικοινωνήστε με τον κατασκευαστή σας για λεπτομέρειες."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Η θερμοκρασία της συσκευής σας είναι πολύ υψηλή και δεν είναι δυνατός ο κατοπτρισμός στην οθόνη μέχρι να μειωθεί"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 8917a82..94ae6cb 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 5a0ed85..ec41583 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor and interact with apps on your behalf."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index bcf0790..2c15524 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 7ebffc6..ff91080 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index b739768..b1dd1f1 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf.‎‏‎‎‏‎"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎Allow‎‏‎‎‏‎"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‏‏‏‎Deny‎‏‎‎‏‎"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎‎Uninstall‎‏‎‎‏‎"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎An app is obscuring the permission request so your response cannot be verified.‎‏‎‎‏‎"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎Tap a feature to start using it:‎‏‎‎‏‎"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎Choose features to use with the accessibility button‎‏‎‎‏‎"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎Choose features to use with the volume key shortcut‎‏‎‎‏‎"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎Weekend‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎Event‎‏‎‎‏‎"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎Sleeping‎‏‎‎‏‎"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎On‎‏‎‎‏‎"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎Off‎‏‎‎‏‎"</string>
     <string name="muted_by" msgid="91464083490094950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="THIRD_PARTY">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is muting some sounds‎‏‎‎‏‎"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‎There\'s an internal problem with your device, and it may be unstable until you factory data reset.‎‏‎‎‏‎"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎There\'s an internal problem with your device. Contact your manufacturer for details.‎‏‎‎‏‎"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎Microphone is blocked‎‏‎‎‏‎"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎Can\'t mirror to display‎‏‎‎‏‎"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎Use a different cable and try again‎‏‎‎‏‎"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‎‏‎Your device is too warm and can\'t mirror to the display until it cools down‎‏‎‎‏‎"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎Cable may not support displays‎‏‎‎‏‎"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎Your USB-C cable may not connect to displays properly‎‏‎‎‏‎"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎Dual screen‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ca9ff13..39a479e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Puede realizar el seguimiento de tus interacciones con una app o un sensor de hardware, así como interactuar con las apps por ti."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Rechazar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una app está cubriendo la solicitud de permiso, por lo que no se puede verificar tu respuesta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Presiona una función para comenzar a usarla:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Selecciona las funciones a utilizar con el botón de accesibilidad"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Selecciona las funciones a usar con las teclas de volumen"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activado"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivado"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe un problema interno con el dispositivo, de modo que el dispositivo puede estar inestable hasta que restablezcas la configuración de fábrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Existe un problema interno con el dispositivo. Comunícate con el fabricante para obtener más información."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"La temperatura del dispositivo es demasiado alta y no se puede duplicar la pantalla hasta que se enfríe"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index b76229a..30ed624 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Puede registrar tus interacciones con una aplicación o un sensor de hardware, así como interactuar con las aplicaciones en tu nombre."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denegar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una aplicación está ocultando la solicitud de permiso, por lo que no se puede verificar tu respuesta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toca una función para empezar a usarla:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Selecciona qué funciones usar con el botón de accesibilidad"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Selecciona qué funciones usar con la tecla de volumen"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Durmiendo"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activado"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivado"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Se ha producido un problema interno en el dispositivo y es posible que este no sea estable hasta que restablezcas el estado de fábrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Se ha producido un problema interno en el dispositivo. Ponte en contacto con el fabricante para obtener más información."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Tu dispositivo está demasiado caliente y no puede proyectar a la pantalla hasta que se enfríe"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 4a8666e..c4ea351 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"See saab jälgida teie suhtlust rakenduse või riistvaraanduriga ja teie eest rakendustega suhelda."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Luba"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Keela"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalli"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Rakendus varjab loataotlust, nii et teie vastust ei saa kinnitada."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Puudutage funktsiooni, et selle kasutamist alustada."</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Valige funktsioonid, mida juurdepääsetavuse nupuga kasutada"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valige helitugevuse nupu otsetee funktsioonid"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Nädalavahetus"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Sündmus"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Magamine"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Sees"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Väljas"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vaigistab teatud helid"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Seadmes ilmnes sisemine probleem ja seade võib olla ebastabiilne seni, kuni lähtestate seadme tehase andmetele."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Seadmes ilmnes sisemine probleem. Üksikasjaliku teabe saamiseks võtke ühendust tootjaga."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Teie seade on liiga kuum ja ei saa ekraanile peegeldada enne, kui see jahtub"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 86eaf0a..26362d5 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Aplikazioekin edo hardware-sentsoreekin dituzun interakzioen jarraipena egin dezake, eta zure izenean beste aplikazio batzuekin interakzioan jardun."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Baimendu"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Ukatu"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalatu"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikazio bat baimen-eskaera oztopatzen ari da eta, ondorioz, ezin da egiaztatu erantzuna."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Eginbide bat erabiltzen hasteko, saka ezazu:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Aukeratu zein eginbide erabili nahi duzun Erabilerraztasuna botoiarekin"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Aukeratu zein eginbide erabili nahi duzun bolumen-botoien lasterbidearekin"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Asteburua"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Gertaera"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Lo egiteko"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktibatuta"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desaktibatuta"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> soinu batzuk isilarazten ari da"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Barneko arazo bat dago zure gailuan eta agian ezegonkor egongo da jatorrizko datuak berrezartzen dituzun arte."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Barneko arazo bat dago zure gailuan. Xehetasunak jakiteko, jarri fabrikatzailearekin harremanetan."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Gailua beroegi dago eta ezingo da pantailan islatu hoztu arte"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index c5a2ee6..c96c580 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -166,7 +166,7 @@
     <string name="httpErrorTimeout" msgid="7446272815190334204">"زمان اتصال به سرور تمام شده است."</string>
     <string name="httpErrorRedirectLoop" msgid="8455757777509512098">"این صفحه دارای تعداد بسیار زیادی تغییر مسیر سرور است."</string>
     <string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"‏پروتکل پشتیبانی نمی‌‎شود."</string>
-    <string name="httpErrorFailedSslHandshake" msgid="546319061228876290">"اتصال امن ایجاد نشد."</string>
+    <string name="httpErrorFailedSslHandshake" msgid="546319061228876290">"اتصال ایمن ایجاد نشد."</string>
     <string name="httpErrorBadUrl" msgid="754447723314832538">"به‌دلیل نامعتبر بودن نشانی اینترنتی، صفحه باز نمی‌شود."</string>
     <string name="httpErrorFile" msgid="3400658466057744084">"دسترسی به فایل انجام نشد."</string>
     <string name="httpErrorFileNotFound" msgid="5191433324871147386">"فایل درخواستی پیدا نشد."</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"این عملکرد می‌تواند با برنامه یا حسگری سخت‌افزاری تعاملاتتان را ردیابی کند و ازطرف شما با برنامه‌ها تعامل داشته باشد."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازه دادن"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مجاز نبودن"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"حذف نصب"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"برنامه‌ای درخواست اجازه را می‌پوشاند و بنابراین نمی‌توان پاسخ شما را تأیید کرد."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"برای استفاده از ویژگی، روی آن ضربه بزنید:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"انتخاب ویژگی‌های موردنظر برای استفاده با دکمه دسترس‌پذیری"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"انتخاب ویژگی‌های موردنظر برای استفاده با میان‌بر کلید میزان صدا"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"آخر هفته"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"رویداد"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"خوابیدن"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"روشن"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"خاموش"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> درحال قطع کردن بعضی از صداهاست"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی داده‌های کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"دستگاهتان یک مشکل داخلی دارد. برای جزئیات آن با سازنده‌تان تماس بگیرید."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"دستگاه بسیار گرم است و تا زمانی‌که خنک نشود نمی‌تواند محتوا را در نمایشگر بازتاب دهد."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏کابل USB-C شما ممکن است به‌درستی به نمایشگرها وصل نشود"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"‫Dual screen"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 4a08b90..c203786 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Se voi seurata toimintaasi sovelluksella tai laitteistoanturilla ja käyttää sovelluksia puolestasi."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Salli"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Estä"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Poista"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Sovellus peittää lupapyynnön, joten vastaustasi ei voi vahvistaa."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Aloita ominaisuuden käyttö napauttamalla sitä:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Valitse ominaisuudet, joita käytetään esteettömyyspainikkeella"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valitse ominaisuudet, joita käytetään äänenvoimakkuuspikanäppäimellä"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Viikonloppuna"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tapahtuma"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Nukkuminen"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Päällä"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Pois päältä"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mykistää joitakin ääniä"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Laitteellasi on sisäinen ongelma, joka aiheuttaa epävakautta. Voit korjata tilanteen palauttamalla tehdasasetukset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Laitteesi yhdistäminen ei onnistu sisäisen virheen takia. Saat lisätietoja valmistajalta."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Laite on liian kuuma eikä voi peilata näyttöön, kunnes se viilenee"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 1b637db..f06d20f 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Cette fonctionnalité peut faire le suivi de vos interactions avec une application ou un capteur matériel et interagir avec des applications en votre nom."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Autoriser"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuser"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Désinstaller"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Une application masque la demande d\'autorisation de sorte que votre réponse ne peut pas être vérifiée."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toucher une fonctionnalité pour commencer à l\'utiliser :"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choisir les fonctionnalités à utiliser à l\'aide du bouton d\'accessibilité"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choisir les fonctionnalités à utiliser avec le raccourci des touches de volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semaine"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Événement"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sommeil"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activé"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Désactivé"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> désactive certains sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne est survenu avec votre appareil. Il se peut qu\'il soit instable jusqu\'à ce que vous le réinitialisiez à ses paramètres par défaut."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Un problème interne est survenu avec votre appareil. Communiquez avec le fabricant pour obtenir plus de détails."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Votre appareil est trop chaud et doit refroidir pour pouvoir dupliquer l\'écran"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 4f8de0c..b69db32 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Le service peut suivre vos interactions avec une application ou un capteur matériel, et interagir avec des applications de votre part."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Autoriser"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuser"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Désinstaller"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Une application masque la demande d\'autorisation. Votre réponse ne peut donc pas être vérifiée."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Appuyez sur une fonctionnalité pour commencer à l\'utiliser :"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choisir les fonctionnalités à utiliser avec le bouton Accessibilité"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choisir les fonctionnalités à utiliser avec le raccourci des touches de volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Week-end"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Événement"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sommeil"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activé"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Désactivé"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> coupe certains sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne lié à votre appareil est survenu. Ce dernier risque d\'être instable jusqu\'à ce que vous rétablissiez la configuration d\'usine."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Un problème interne lié à votre appareil est survenu. Veuillez contacter le fabricant pour en savoir plus."</string>
@@ -2337,15 +2341,16 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Votre appareil est trop chaud et ne peut pas se dupliquer sur l\'écran tant qu\'il n\'a pas refroidi."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string>
-    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Double écran"</string>
-    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Double écran activé"</string>
+    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
+    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen activé"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher du contenu"</string>
     <string name="concurrent_display_notification_thermal_title" msgid="5921609404644739229">"L\'appareil est trop chaud"</string>
-    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"Double écran n\'est pas disponible, car votre téléphone surchauffe"</string>
-    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"Double écran n\'est pas disponible"</string>
-    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"Double écran n\'est pas disponible, car Économiseur de batterie est activé. Vous pouvez désactiver cette option dans les paramètres."</string>
+    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"Dual Screen n\'est pas disponible, car votre téléphone surchauffe"</string>
+    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"Dual Screen n\'est pas disponible"</string>
+    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"Dual Screen n\'est pas disponible, car l\'économiseur de batterie est activé. Vous pouvez désactiver cette option dans les paramètres."</string>
     <string name="device_state_notification_settings_button" msgid="691937505741872749">"Accédez aux paramètres"</string>
     <string name="device_state_notification_turn_off_button" msgid="6327161707661689232">"Désactiver"</string>
     <string name="keyboard_layout_notification_selected_title" msgid="1202560174252421219">"<xliff:g id="DEVICE_NAME">%s</xliff:g> configuré"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index b25cfcb..f26dbfb 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode facer un seguimento das túas interaccións cunha aplicación ou cun sensor de hardware, así como interactuar por ti coas aplicacións."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denegar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Hai unha aplicación que está ocultando a solicitude de permiso, polo que non se pode verificar a túa resposta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tocar unha función para comezar a utilizala:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escoller as funcións que queres utilizar co botón Accesibilidade"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolle as funcións que queres utilizar co atallo da tecla de volume"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Durmindo"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activada"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivada"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando algúns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Produciuse un erro interno no teu dispositivo e quizais funcione de maneira inestable ata o restablecemento dos datos de fábrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Produciuse un erro interno co teu dispositivo. Contacta co teu fabricante para obter máis información."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O teu dispositivo está demasiado quente; mentres non arrefríe, non se poderá proxectar contido na pantalla"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index fae5ebc..d64e33a 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"તે ઍપ અથવા હાર્ડવેર સેન્સર વડે તમારી ક્રિયાપ્રતિક્રિયાને ટ્રૅક કરી શકે છે અને તમારા વતી ઍપ સાથે ક્રિયાપ્રતિક્રિયા કરી શકે છે."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"મંજૂરી આપો"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"નકારો"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"અનઇન્સ્ટૉલ કરો"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"કોઈ ઍપ પરવાનગીની વિનંતીને ઢાંકી રહી છે, તેથી તમારા પ્રતિસાદની ચકાસણી કરી શકાતી નથી."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"સુવિધાનો ઉપયોગ શરૂ કરવા તેના પર ટૅપ કરો:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ઍક્સેસિબિલિટી બટન વડે તમે ઉપયોગમાં લેવા માગો છો તે સુવિધાઓ પસંદ કરો"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"વૉલ્યૂમ કી શૉર્ટકટ વડે તમે ઉપયોગમાં લેવા માગો છો તે સુવિધાઓ પસંદ કરો"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"સપ્તાહાંત"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ઇવેન્ટ"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"નિષ્ક્રિય"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ચાલુ છે"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"બંધ છે"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> અમુક અવાજોને મ્યૂટ કરે છે"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે અને જ્યાં સુધી તમે ફેક્ટરી ડેટા ફરીથી સેટ કરશો નહીં ત્યાં સુધી તે અસ્થિર રહી શકે છે."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે. વિગતો માટે તમારા નિર્માતાનો સંપર્ક કરો."</string>
@@ -2011,7 +2015,7 @@
     <string name="app_category_image" msgid="7307840291864213007">"ફોટો અને છબીઓ"</string>
     <string name="app_category_social" msgid="2278269325488344054">"સામાજિક અને સંચાર"</string>
     <string name="app_category_news" msgid="1172762719574964544">"સમાચાર અને સામાયિકો"</string>
-    <string name="app_category_maps" msgid="6395725487922533156">"Maps અને નેવિગેશન"</string>
+    <string name="app_category_maps" msgid="6395725487922533156">"Maps અને નૅવિગેશન"</string>
     <string name="app_category_productivity" msgid="1844422703029557883">"ઉત્પાદકતા"</string>
     <string name="app_category_accessibility" msgid="6643521607848547683">"ઍક્સેસિબિલિટી"</string>
     <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"ડિવાઇસ સ્ટૉરેજ"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"તમારું ડિવાઇસ ખૂબ જ ગરમ છે અને જ્યાં સુધી તે ઠંડું ન પડે ત્યાં સુધી મિરર કરી શકશે નહીં"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index d4eb380..61db784 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"यह आपके और किसी ऐप्लिकेशन या हार्डवेयर सेंसर के बीच होने वाले इंटरैक्शन को ट्रैक कर सकता है और आपकी तरफ़ से ऐप्लिकेशन के साथ इंटरैक्ट कर सकता है."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमति दें"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"इंकार करें"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइंस्टॉल करें"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ऐप्लिकेशन की वजह से, अनुमति का अनुरोध समझने में परेशानी हो रही है. इसलिए, आपके जवाब की पुष्टि नहीं की जा सकी."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"किसी सुविधा का इस्तेमाल करने के लिए, उस पर टैप करें:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"सुलभता बटन पर टैप करके, इस्तेमाल करने के लिए सुविधाएं चुनें"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"चुनें कि आवाज़ कम या ज़्यादा करने वाले बटन के शॉर्टकट से आप किन सुविधाओं का इस्तेमाल करना चाहते हैं"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"सप्ताहांत"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"इवेंट"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"सोते समय"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"चालू है"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"बंद है"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> कुछ आवाज़ें म्‍यूट कर रहा है"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"आपके डिवाइस में कोई अंदरूनी समस्या है और यह तब तक ठीक नहीं होगी जब तक आप फ़ैक्‍टरी डेटा रीसेट नहीं करते."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"आपके डिवाइस के साथ कोई आंतरिक गड़बड़ी हुई. विवरणों के लिए अपने निर्माता से संपर्क करें."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"आपका डिवाइस बहुत गर्म है. इसलिए, इसके ठंडा होने तक दूसरे डिसप्ले पर इसकी स्क्रीन शेयर नहीं की जा सकती"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 977f881..4b9b7bc 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može pratiti vaše interakcije s aplikacijama ili senzorom uređaja i stupati u interakciju s aplikacijama u vaše ime."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dopusti"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija prekriva upit za dopuštenje pa se vaš odgovor ne može potvrditi."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite značajku da biste je počeli koristiti:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odabir značajki za upotrebu pomoću gumba za Pristupačnost"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odabir značajki za upotrebu pomoću prečaca tipki za glasnoću"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Na vašem uređaju postoji interni problem i možda neće biti stabilan dok ga ne vratite na tvorničko stanje."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Na vašem uređaju postoji interni problem. Obratite se proizvođaču za više pojedinosti."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je previše zagrijan i ne može se zrcaliti na zaslon dok se ne ohladi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 9a389db..5827300 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Követheti az alkalmazásokkal és hardveres érzékelőkkel való interakcióit, és műveleteket végezhet az alkalmazásokkal az Ön nevében."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Engedélyezés"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tiltás"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Eltávolítás"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Az egyik alkalmazás eltakarja az engedélykérelmet, így az Ön válasza nem ellenőrizhető."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Koppintson valamelyik funkcióra a használatához:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Kiválaszthatja a Kisegítő lehetőségek gombbal használni kívánt funkciókat"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Kiválaszthatja a hangerőgombbal használni kívánt funkciókat"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hétvége"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Esemény"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Alvás"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Bekapcsolva"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kikapcsolva"</string>
     <string name="muted_by" msgid="91464083490094950">"A(z) <xliff:g id="THIRD_PARTY">%1$s</xliff:g> lenémít néhány hangot"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Belső probléma van az eszközzel, és instabil lehet, amíg vissza nem állítja a gyári adatokat."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Belső probléma van az eszközzel. A részletekért vegye fel a kapcsolatot a gyártóval."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Az eszköz túl meleg – csak a lehűlése után tud tükrözni a kijelzőre."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 9c2c2c5..9e313a6 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Կարող է հետագծել ձեր գործողությունները հավելվածներում և սարքակազմի սենսորների վրա, ինչպես նաև հավելվածներում կատարել գործողություններ ձեր անունից։"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Թույլատրել"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Մերժել"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ընտրեք՝ որ գործառույթն օգտագործել"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Ընտրեք գործառույթները, որոնք կբացվեն «Հատուկ գործառույթներ» կոճակի միջոցով"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Ընտրեք գործառույթները, որոնք կբացվեն ձայնի կարգավորման կոճակի միջոցով"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Շաբաթ-կիրակի"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Միջոցառում"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Քնի ժամանակ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Միացված է"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Անջատված է"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ն անջատում է որոշ ձայներ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Սարքում ներքին խնդիր է առաջացել և այն կարող է կրկնվել, մինչև չվերականգնեք գործարանային կարգավորումները:"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Սարքում ներքին խնդիր է առաջացել: Մանրամասների համար կապվեք արտադրողի հետ:"</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Ձեր սարքը գերտաքացել է և չի կարող հայելապատճենել էկրանը, մինչև ջերմաստիճանը չնվազի"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 5bc62c2..b4044aa 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Voice Access dapat melacak interaksi Anda dengan aplikasi atau sensor hardware, dan berinteraksi dengan aplikasi untuk Anda."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Izinkan"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tolak"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstal"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikasi menghalangi permintaan izin sehingga respons Anda tidak dapat diverifikasi."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ketuk fitur untuk mulai menggunakannya:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pilih fitur yang akan digunakan dengan tombol aksesibilitas"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pilih fitur yang akan digunakan dengan pintasan tombol volume"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Akhir pekan"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Acara"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Tidur"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktif"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Nonaktif"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mematikan beberapa suara"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ada masalah dengan perangkat. Hal ini mungkin membuat perangkat jadi tidak stabil dan perlu dikembalikan ke setelan pabrik."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Ada masalah dengan perangkat. Hubungi produsen perangkat untuk informasi selengkapnya."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Perangkat terlalu panas dan tidak dapat mencerminkan ke layar sampai cukup dingin"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f666308..062b4bc 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Það getur fylgst með samskiptum þínum við forrit eða skynjara vélbúnaðar og haft samskipti við forrit fyrir þína hönd."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Leyfa"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Hafna"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Fjarlægja"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Forrit er að fela heimildarbeiðnina svo ekki er hægt að staðfesta svarið þitt."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ýttu á eiginleika til að byrja að nota hann:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Veldu eiginleika sem á að nota með aðgengishnappinum"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Veldu eiginleika sem á að nota með flýtileið hljóðstyrkstakka"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Helgi"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Viðburður"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Svefn"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Kveikt"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Slökkt"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> þaggar í einhverjum hljóðum"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Innra vandamál kom upp í tækinu og það kann að vera óstöðugt þangað til þú núllstillir það."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Innra vandamál kom upp í tækinu. Hafðu samband við framleiðanda til að fá nánari upplýsingar."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Tækið er of heitt og getur ekki speglað á skjáinn fyrr en það kólnar"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 9ffadbe..2e024cb 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Può tenere traccia delle tue interazioni con un\'app o un sensore hardware e interagire con app per tuo conto."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Consenti"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Rifiuta"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Disinstalla"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Un\'app nasconde la tua richiesta di autorizzazione, per cui non abbiamo potuto verificare la tua risposta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tocca una funzionalità per iniziare a usarla:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Scegli le funzionalità da usare con il pulsante Accessibilità"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Scegli le funzionalità da usare con la scorciatoia dei tasti del volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fine settimana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Notte"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Si è verificato un problema interno con il dispositivo. Per informazioni dettagliate, contatta il produttore."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Il tuo dispositivo è troppo caldo e non può eseguire il mirroring al display finché non si raffredda"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 53ec382..e5e85c4 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"אפשרות למעקב אחר האינטראקציה שלך עם אפליקציות או חיישני חומרה, וביצוע אינטראקציה בשמך."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"אישור"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"עדיף שלא"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"הסרה"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"אפליקציה מסתירה את בקשת ההרשאה כך שלא ניתן לאמת את התשובה שלך."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"יש להקיש על תכונה כדי להתחיל להשתמש בה:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"בחירת תכונה לשימוש עם לחצן הנגישות"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"בחירת תכונות לשימוש עם מקש הקיצור לעוצמת הקול"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"סוף השבוע"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"אירוע"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"שינה"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"מצב פעיל"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"מצב מושבת"</string>
     <string name="muted_by" msgid="91464083490094950">"חלק מהצלילים מושתקים על ידי <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"קיימת בעיה פנימית במכשיר שלך, וייתכן שהוא לא יתפקד כראוי עד שיבוצע איפוס לנתוני היצרן."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"קיימת בעיה פנימית במכשיר שלך. לקבלת פרטים, יש ליצור קשר עם היצרן."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"המכשיר שלך חם מדי. אי אפשר לשקף למסך עד שהמכשיר יתקרר"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index ab865aa..6e86b57 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"アプリやハードウェア センサーの操作を記録したり、自動的にアプリを操作したりできます。"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"許可"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"許可しない"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"アンインストール"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"権限のリクエストを遮っているアプリがあるため、同意の回答を確認できません。"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"使用を開始する機能をタップ:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ユーザー補助機能ボタンで使用する機能の選択"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"音量ボタンのショートカットで使用する機能の選択"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"予定"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠中"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ON"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"OFF"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> により一部の音はミュートに設定"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"デバイスで内部的な問題が発生しました。データが初期化されるまで不安定になる可能性があります。"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"デバイスで内部的な問題が発生しました。詳しくはメーカーにお問い合わせください。"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"デバイスが熱すぎるため、温度が下がるまでディスプレイにミラーリングできません"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 27f596b..34f56546 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"მას შეუძლია თვალი მიადევნოს თქვენს ინტერაქციებს აპის ან აპარატურის სენსორის საშუალებით, ასევე, თქვენი სახელით აწარმოოს აპებთან ინტერაქცია."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"დაშვება"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"უარყოფა"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"დეინსტალაცია"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"აპი მალავს ნებართვის მოთხოვნას, ასე რომ, თქვენი პასუხი ვერ დადასტურდება."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"შეეხეთ ფუნქციას მისი გამოყენების დასაწყებად:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"აირჩიეთ ფუნქციები, რომელთა გამოყენებაც გსურთ მარტივი წვდომის ღილაკით"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"აირჩიეთ ფუნქციები, რომელთა გამოყენებაც გსურთ ხმის ღილაკის მალსახმობით"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"შაბათ-კვირა"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"მოვლენა"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ძილისას"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ჩართული"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"გამორთული"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ზოგიერთ ხმას ადუმებს"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ფიქსირდება თქვენი მ ოწყობილობის შიდა პრობლემა და შეიძლება არასტაბილური იყოს, სანამ ქარხნულ მონაცემების არ განაახლებთ."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ფიქსირდება თქვენი მოწყობილობის შიდა პრობლემა. დეტალებისათვის, მიმართეთ თქვენს მწარმოებელს."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"თქვენი მოწყობილობა ძალიან თბილია და ვერ ასახავს ეკრანზე სანამ არ გაგრილდება"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1faea61..839f3f9 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1690,7 +1690,7 @@
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Жою"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string>
     <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"Жоғары дыбыс деңгейінде тыңдай бересіз бе?\n\nҚұлақаспаптың жоғары дыбыс деңгейі ұсынылған уақыттан ұзақ қосылып тұрды. Есту мүшеңізге зияны тиюі мүмкін."</string>
-    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"Қатты дыбыс анықталды\n\nҚұлақаспаптың жоғары дыбыс деңгейі ұсынылған уақыттан ұзақ қосылып тұрды. Есту мүшеңізге зияны тиюі мүмкін."</string>
+    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"Қатты дыбыс анықталды\n\nҚұлақаспаптың дыбысы ұсынылған деңгейден асып кетті. Есту мүшеңізге зияны тиюі мүмкін."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Арнайы мүмкіндіктердің жылдам пәрмені іске қосылсын ба?"</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ол қолданбамен немесе жабдық датчигімен істеген тапсырмаларыңызды бақылайды және қолданбаларды сіздің атыңыздан пайдаланады."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Рұқсат ету"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Тыйым салу"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Жою"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Қолданба рұқсат сұрауын жасырып тұрғандықтан, жауабыңыз расталмайды."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Функцияны пайдалана бастау үшін түртіңіз:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"\"Арнайы мүмкіндіктер\" түймесімен қолданылатын функцияларды таңдаңыз"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дыбыс деңгейі пернелері тіркесімімен қолданылатын функцияларды таңдаңыз"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Демалыс күндері"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Іс-шара"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ұйқы режимі"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Қосулы"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Өшірулі"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> кейбір дыбыстарды өшіруде"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Құрылғыңыз тым қызып кетті, сондықтан ол суымайынша, дисплейге экран көшірмесін көрсете алмайды."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 470c39e..9a29150 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"វា​អាចតាមដានអន្តរកម្មរបស់អ្នកជាមួយនឹងកម្មវិធី ឬសេនស័រហាតវែរ និងធ្វើអន្តរកម្ម​ជាមួយកម្មវិធីនានា​ជំនួសឱ្យអ្នក។"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"អនុញ្ញាត"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"បដិសេធ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"លុប"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"កម្មវិធីមួយកំពុងបិទបាំងសំណើសុំការអនុញ្ញាត ដូច្នេះមិនអាចផ្ទៀងផ្ទាត់ការឆ្លើយតបរបស់អ្នកបានទេ។"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ចុចមុខងារណាមួយ ដើម្បចាប់ផ្ដើមប្រើ៖"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ជ្រើសរើស​មុខងារ ដើម្បីប្រើ​ជាមួយ​ប៊ូតុង​ភាពងាយស្រួល"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ជ្រើសរើស​មុខងារ ដើម្បីប្រើ​ជាមួយផ្លូវកាត់គ្រាប់ចុច​កម្រិតសំឡេង"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ចុងសប្ដាហ៍"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ព្រឹត្តិការណ៍"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"កំពុងដេក"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"បើក"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"បិទ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> កំពុង​បិទសំឡេង​មួយចំនួន"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ហើយវាអ្នកមិនមានស្ថេរភាព រហូតទាល់តែអ្នកកំណត់ដូចដើមវិញទាំងស្រុង។"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ទំនាក់ទំនងក្រុមហ៊ុនផលិតឧបករណ៍របស់អ្នកសម្រាប់ព័ត៌មានបន្ថែម។"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវ​បានទប់ស្កាត់"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ឧបករណ៍របស់អ្នកក្ដៅពេក និងមិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ រហូតទាល់តែវាចុះត្រជាក់សិន"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នក​ប្រហែលជា​មិនអាចភ្ជាប់​ផ្ទាំងអេក្រង់​បានត្រឹមត្រូវទេ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index e2f24f6..9f1700d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1689,8 +1689,8 @@
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ತೆಗೆದುಹಾಕು"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ವಾಲ್ಯೂಮ್‌ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾದ ಮಟ್ಟಕ್ಕಿಂತಲೂ ಹೆಚ್ಚು ಮಾಡಬೇಕೆ?\n\nದೀರ್ಘ ಅವಧಿಯವರೆಗೆ ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್‌ನಲ್ಲಿ ಆಲಿಸುವುದರಿಂದ ನಿಮ್ಮ ಆಲಿಸುವಿಕೆ ಸಾಮರ್ಥ್ಯಕ್ಕೆ ಹಾನಿಯುಂಟು ಮಾಡಬಹುದು."</string>
-    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್‌ನಲ್ಲಿ ಆಲಿಸುವುದನ್ನು ಮುಂದುವರಿಸಬೇಕೇ?\n\nಹೆಡ್‌ಫೋನ್‌ನ ವಾಲ್ಯೂಮ್ ಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ಹೆಚ್ಚಿನ ಸಮಯದವರೆಗೆ ಅಧಿಕವಾಗಿದ್ದು, ಇದರಿಂದ ನಿಮ್ಮ ಶ್ರವಣ ಶಕ್ತಿಗೆ ಹಾನಿಯಾಗಬಹುದು"</string>
-    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ದೊಡ್ಡ ಧ್ವನಿ ಪತ್ತೆಯಾಗಿದೆ\n\nಹೆಡ್‌ಫೋನ್ ವಾಲ್ಯೂಮ್ ಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ಹೆಚ್ಚಾಗಿದ್ದು, ಇದರಿಂದ ನಿಮ್ಮ ಶ್ರವಣ ಶಕ್ತಿಗೆ ಹಾನಿಯಾಗಬಹುದು"</string>
+    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್‌ನಲ್ಲಿ ಆಲಿಸುವುದನ್ನು ಮುಂದುವರಿಸಬೇಕೇ?\n\nಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ದೀರ್ಘಕಾಲ ಹೆಡ್‌ಫೋನ್‌ನ ವಾಲ್ಯೂಮ್ ಹೆಚ್ಚಿಗೆ ಇದ್ದು, ಇದರಿಂದ ನಿಮ್ಮ ಶ್ರವಣ ಶಕ್ತಿಗೆ ಹಾನಿಯಾಗಬಹುದು"</string>
+    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ದೊಡ್ಡ ಧ್ವನಿ ಪತ್ತೆಯಾಗಿದೆ\n\nಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ದೀರ್ಘಕಾಲ ಹೆಡ್‌ಫೋನ್ ವಾಲ್ಯೂಮ್ ಹೆಚ್ಚಿಗೆ ಇದ್ದು, ಇದರಿಂದ ನಿಮ್ಮ ಶ್ರವಣ ಶಕ್ತಿಗೆ ಹಾನಿಯಾಗಬಹುದು"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಶಾರ್ಟ್‌ಕಟ್ ಬಳಸುವುದೇ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್‌ಗಳನ್ನು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಿದರೆ ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ವೈಶಿಷ್ಟ್ಯವೊಂದು ಪ್ರಾರಂಭವಾಗುತ್ತದೆ."</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ವೈಶಿಷ್ಟ್ಯಗಳಿಗಾಗಿ ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಮಾಡಬೇಕೇ?"</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ಇದು ಆ್ಯಪ್ ಅಥವಾ ಹಾರ್ಡ್‌ವೇರ್ ಸೆನ್ಸರ್‌ನ ಜೊತೆಗಿನ ನಿಮ್ಮ ಸಂವಹನಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಬಹುದು, ಮತ್ತು ನಿಮ್ಮ ಪರವಾಗಿ ಆ್ಯಪ್‌ಗಳ ಜೊತೆ ಸಂವಹನ ನಡೆಸಬಹುದು."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ಅನುಮತಿಸಿ"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ನಿರಾಕರಿಸಿ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ಆ್ಯಪ್‌ವೊಂದು ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ವೈಶಿಷ್ಟ್ದ ಬಳಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸಲು ಅದನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಬಟನ್ ಜೊತೆಗೆ ಬಳಸಲು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ವಾಲ್ಯೂಮ್ ಕೀ ಶಾರ್ಟ್‌ಕಟ್ ಜೊತೆಗೆ ಬಳಸಲು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ವಾರಾಂತ್ಯ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ಈವೆಂಟ್"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ನಿದ್ರೆಯ ಸಮಯ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ಆನ್ ಆಗಿದೆ"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ಆಫ್ ಆಗಿದೆ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ಧ್ವನಿ ಮ್ಯೂಟ್ ಮಾಡುತ್ತಿದ್ದಾರೆ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ ಹಾಗೂ ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾವನ್ನು ರೀಸೆಟ್ ಮಾಡುವವರೆಗೂ ಅದು ಅಸ್ಥಿರವಾಗಬಹುದು."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ. ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ತಯಾರಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
@@ -2036,7 +2040,7 @@
     <string name="autofill_update_title_with_type" msgid="5264152633488495704">"<xliff:g id="TYPE">%1$s</xliff:g> ಅನ್ನು "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೆ?"</string>
     <string name="autofill_update_title_with_2types" msgid="1797514386321086273">"<xliff:g id="TYPE_0">%1$s</xliff:g> ಮತ್ತು <xliff:g id="TYPE_1">%2$s</xliff:g> ಅನ್ನು "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೆ?"</string>
     <string name="autofill_update_title_with_3types" msgid="8285767070604652626">"ಈ ಮುಂದಿನ ಐಟಂಗಳನ್ನು "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೆ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ಮತ್ತು <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
-    <string name="autofill_save_yes" msgid="8035743017382012850">"ಉಳಿಸಿ"</string>
+    <string name="autofill_save_yes" msgid="8035743017382012850">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="autofill_save_no" msgid="9212826374207023544">"ಬೇಡ"</string>
     <string name="autofill_save_notnow" msgid="2853932672029024195">"ಸದ್ಯಕ್ಕೆ ಬೇಡ"</string>
     <string name="autofill_save_never" msgid="6821841919831402526">"ಎಂದೂ ಬೇಡ"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ನಿಮ್ಮ ಸಾಧನವು ತುಂಬಾ ಬಿಸಿಯಾಗಿದೆ ಮತ್ತು ಅದು ತಣ್ಣಗಾಗುವವರೆಗೆ ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್‌ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್‌ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 4ed97fe..907e802 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"앱 또는 하드웨어 센서와의 상호작용을 추적할 수 있으며 나를 대신해 앱과 상호작용할 수 있습니다."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"허용"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"거부"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"제거"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"앱에서 권한 요청을 가려서 응답을 확인할 수 없습니다."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"기능을 사용하려면 탭하세요"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"접근성 버튼으로 사용할 기능 선택"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"볼륨 키 단축키로 사용할 기능 선택"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"주말"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"캘린더 일정"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"수면 시간"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"사용"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"사용 중지"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"사용 중인 기기 내부에 문제가 발생했습니다. 자세한 내용은 제조업체에 문의하세요."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"기기의 온도가 너무 높아서 온도가 내려갈 때까지 화면에 미러링할 수 없습니다."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 421284b..5efcfc4b 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Кызмат колдонмодо жасаган аракеттериңизге же түзмөктүн сенсорлоруна көз салып, сиздин атыңыздан буйруктарды берет."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ооба"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Жок"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Чыгарып салуу"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Колдонмо уруксат суроону жашырып койгондуктан, жообуңузду ырастоо мүмкүн эмес."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Функцияны колдонуп баштоо үчүн аны таптап коюңуз:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Атайын мүмкүнчүлүктөр баскычы менен колдонгуңуз келген функцияларды тандаңыз"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Үндү катуулатуу/акырындатуу баскычтары менен кайсы функцияларды иштеткиңиз келет?"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Дем алыш"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Иш-чара"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Уйку режими"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Күйүк"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Өчүк"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> айрым үндөрдү өчүрүүдө"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Түзмөгүңүздө ички көйгөй бар жана ал баштапкы абалга кайтарылмайынча туруктуу иштебей коюшу мүмкүн."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Түзмөгүңүздө ички көйгөй бар. Анын чоо-жайын билүү үчүн өндүрүүчүңүзгө кайрылыңыз."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Түзмөгүңүз өтө ысып кетти жана ал муздамайынча башка экранга чыгара албайт"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index c743fc0..d0f698d 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ມັນສາມາດຕິດຕາມການໂຕ້ຕອບຂອງທ່ານກັບແອັບ ຫຼື ເຊັນເຊີຮາດແວໃດໜຶ່ງ ແລະ ໂຕ້ຕອບກັບແອັບໃນນາມຂອງທ່ານໄດ້."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ອະນຸຍາດ"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ປະຕິເສດ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ຖອນການຕິດຕັ້ງ"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ແອັບໜຶ່ງກຳລັງປິດບັງຄຳຮ້ອງຂໍການອະນຸຍາດ ດັ່ງນັ້ນຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນຄຳຕອບຂອງທ່ານໄດ້."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ແຕະໃສ່ຄຸນສົມບັດໃດໜຶ່ງເພື່ອເລີ່ມການນຳໃຊ້ມັນ:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ເລືອກຄຸນສົມບັດເພື່ອໃຊ້ກັບປຸ່ມການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ເລືອກຄຸນສົມບັດເພື່ອໃຊ້ກັບທາງລັດປຸ່ມລະດັບສຽງ"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ທ້າຍອາທິດ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ການນັດໝາຍ"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ການນອນ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ເປີດຢູ່"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ປິດຢູ່"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ປິດສຽງບາງຢ່າງໄວ້"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ມີ​ບັນ​ຫາ​ພາຍ​ໃນ​ກັບ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ, ແລະ​ມັນ​ອາດ​ຈະ​ບໍ່​ສະ​ຖຽນ​ຈົນ​ກວ່າ​ທ່ານ​ຕັ້ງ​ເປັນ​ຂໍ້​ມູນ​ໂຮງ​ງານ​ຄືນ​ແລ້ວ."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ມີ​ບັນ​ຫາ​ພາຍ​ໃນ​ກັບ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ. ຕິດ​ຕໍ່ຜູ້​ຜະ​ລິດ​ຂອງ​ທ່ານ​ສຳ​ລັບ​ລາຍ​ລະ​ອຽດ​ຕ່າງໆ."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ອຸປະກອນຂອງທ່ານຮ້ອນເກີນໄປ ແລະ ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້ຈົນກວ່າມັນຈະເຢັນລົງ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index ff665c5..6b6bc50 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Naudojant šią funkciją galima stebėti jūsų sąveiką su programa ar aparatinės įrangos jutikliu ir sąveikauti su programomis jūsų vardu."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Leisti"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Atmesti"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Pašalinti"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Programa užstoja leidimo užklausą, todėl negalima patvirtinti jūsų atsakymo."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Norėdami naudoti funkciją, palieskite ją:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Funkcijų, kurioms bus naudojamas pritaikomumo mygtukas, pasirinkimas"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Funkcijų, kurioms bus naudojamas garsumo spartusis klavišas, pasirinkimas"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Savaitgalį"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Įvykis"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Miegas"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Įjungti"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Išjungti"</string>
     <string name="muted_by" msgid="91464083490094950">"„<xliff:g id="THIRD_PARTY">%1$s</xliff:g>“ nutildo kai kuriuos garsus"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Iškilo vidinė su jūsų įrenginiu susijusi problema, todėl įrenginys gali veikti nestabiliai, kol neatkursite gamyklinių duomenų."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Iškilo vidinė su jūsų įrenginiu susijusi problema. Jei reikia išsamios informacijos, susisiekite su gamintoju."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Jūsų įrenginys per daug įkaitęs ir negali bendrinti ekrano vaizdo, kol atvės"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 4d369aa..931e681 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Tā var izsekot jūsu mijiedarbību ar lietotni vai aparatūras sensoru un mijiedarboties ar lietotnēm jūsu vārdā."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Atļaut"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Neatļaut"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Atinstalēt"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Kāda lietotne padara atļaujas pieprasījumu nesaprotamu, tāpēc nevar verificēt jūsu atbildi."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Pieskarieties funkcijai, lai sāktu to izmantot"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Izvēlieties funkcijas, ko izmantot ar pieejamības pogu"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Izvēlieties funkcijas, ko izmantot ar skaļuma pogu saīsni"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Nedēļas nogalē"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Pasākums"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Gulēšana"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ieslēgta"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Izslēgta"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izslēdz noteiktas skaņas"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Jūsu ierīcē ir radusies iekšēja problēma, un ierīce var darboties nestabili. Lai to labotu, veiciet rūpnīcas datu atiestatīšanu."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Jūsu ierīcē ir radusies iekšēja problēma. Lai iegūtu plašāku informāciju, lūdzu, sazinieties ar ražotāju."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Jūsu ierīce ir pārāk silta, un to nevar spoguļot displejā, kamēr tā nav atdzisusi."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 29c9de8..420adfe 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Може да ја следи вашата интеракција со апликациите или со хардверските сензори и да врши интеракција со апликациите во ваше име."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дозволи"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Одбиј"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Допрете на функција за да почнете да ја користите:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Изберете ги функциите што ќе ги користите со копчето за пристапност"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Изберете ги функциите што ќе ги користите со кратенката за копчето за јачина на звук"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Викенд"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Настан"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Спиење"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Вклучено"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Исклучено"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> исклучи некои звуци"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Настана внатрешен проблем со уредот и може да биде нестабилен сè додека не ресетирате на фабричките податоци."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Настана внатрешен проблем со уредот. Контактирајте го производителот за детали."</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Вашиот уред е премногу топол и не може да се отсликува на екранот додека не се излади"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 46587ee..466dab6 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ഇതിന് ഒരു ആപ്പുമായോ ഹാർഡ്‌വെയർ സെൻസറുമായോ ഉള്ള നിങ്ങളുടെ ആശയവിനിമയങ്ങൾ ട്രാക്ക് ചെയ്യാനും നിങ്ങളുടെ പേരിൽ ആശയവിനിമയം നടത്താനും കഴിയും."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"അനുവദിക്കൂ"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"നിരസിക്കുക"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"അൺഇൻസ്‌റ്റാൾ ചെയ്യുക"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ഒരു ആപ്പ്, അനുമതി അഭ്യർത്ഥന മറയ്‌ക്കുന്നതിനാൽ നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ഉപയോഗിച്ച് തുടങ്ങാൻ ഫീച്ചർ ടാപ്പ് ചെയ്യുക:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ഉപയോഗസഹായി ബട്ടണിന്റെ സഹായത്തോടെ, ഉപയോഗിക്കാൻ ഫീച്ചറുകൾ തിരഞ്ഞെടുക്കുക"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"വോളിയം കീ കുറുക്കുവഴിയിലൂടെ ഉപയോഗിക്കാൻ ഫീച്ചറുകൾ തിരഞ്ഞെടുക്കുക"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"വാരാന്ത്യം"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ഇവന്റ്"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ഉറക്കം"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ഓണാണ്"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ഓഫാണ്"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ചില ശബ്‌ദങ്ങൾ മ്യൂട്ട് ചെയ്യുന്നു"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്‌നമുണ്ട്, ഫാക്‌ടറി വിവര പുനഃസജ്ജീകരണം ചെയ്യുന്നതുവരെ ഇതു അസ്ഥിരമായിരിക്കാനിടയുണ്ട്."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്‌നമുണ്ട്. വിശദാംശങ്ങൾക്കായി നിർമ്മാതാവിനെ ബന്ധപ്പെടുക."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്‌തു"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"നിങ്ങളുടെ ഉപകരണത്തിന് ചൂട് വളരെ കൂടുതലാണ്, അത് തണുക്കുന്നത് വരെ ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്‌പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 62a162e..3cb9cb2 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Энэ нь таны апп болон техник хангамжийн мэдрэгчтэй хийх харилцан үйлдлийг хянах болон таны өмнөөс апптай харилцан үйлдэл хийх боломжтой."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Зөвшөөрөх"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Татгалзах"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Устгах"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Апп зөвшөөрлийн хүсэлтийг хааж байгаа тул таны хариултыг баталгаажуулах боломжгүй."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Үүнийг ашиглаж эхлэхийн тулд онцлог дээр товшино уу:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Хандалтын товчлуурын тусламжтай ашиглах онцлогуудыг сонгоно уу"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дууны түвшний түлхүүрийн товчлолын тусламжтай ашиглах онцлогуудыг сонгоно уу"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Амралтын өдөр"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Үйл явдал"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Унтлагын цаг"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Асаалттай"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Унтраалттай"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Таны төхөөрөмжид дотоод алдаа байна. Дэлгэрэнгүй мэдээлэл авахыг хүсвэл үйлдвэрлэгчтэйгээ холбоо барина уу."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Таны төхөөрөмж хэт халсан бөгөөд үүнийг хөрөх хүртэл дэлгэцэд тусгал үүсгэх боломжгүй"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 87227ee..b20754b 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"हे तुमचा ॲप किंवा हार्डवेअर सेन्सरसोबतचा परस्‍परसंवाद ट्रॅक करू शकते आणि इतर ॲप्ससोबत तुमच्या वतीने संवाद साधू शकते."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमती द्या"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नकार द्या"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइंस्टॉल करा"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"परवानगी मागणारी विनंती अ‍ॅपमुळे अस्पष्‍ट होत असल्‍याने, तुमच्या प्रतिसादाची पडताळणी केली जाऊ शकत नाही."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"वैशिष्ट्य वापरणे सुरू करण्यासाठी त्यावर टॅप करा:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"अ‍ॅक्सेसिबिलिटी बटणासोबत वापरायची असलेली ॲप्स निवडा"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"व्‍हॉल्‍यूम की शॉर्टकटसोबत वापरायची असलेली ॲप्स निवडा"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"आठवड्याच्या शेवटी"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"इव्‍हेंट"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"झोपताना"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"सुरू आहे"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"बंद आहे"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> काही ध्‍वनी म्‍यूट करत आहे"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"आपल्‍या डिव्‍हाइसमध्‍ये अंतर्गत समस्‍या आहे आणि तुमचा फॅक्‍टरी डेटा रीसेट होईपर्यंत ती अस्‍थिर असू शकते."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"आपल्‍या डिव्‍हाइसमध्‍ये अंतर्गत समस्‍या आहे. तपशीलांसाठी आपल्‍या निर्मात्याशी संपर्क साधा."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"मायक्रोफोन ब्लॉक केलेला आहे"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेवर मिरर करू शकत नाही"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"वेगळी केबल वापरून पुन्हा प्रयत्न करा"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"तुमचे डिव्हाइस खूप गरम आहे आणि ते थंड होईपर्यंत डिस्प्लेमध्ये मिरर करू शकत नाही"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"केबल कदाचित डिस्प्लेना सपोर्ट करणार नाही"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तुमची USB-C केबल कदाचित डिस्प्लेना योग्यरीत्या कनेक्ट होणार नाही"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 7794150..6b1742c 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ciri ini boleh menjejaki interaksi anda dengan apl atau penderia perkakasan dan berinteraksi dengan apl bagi pihak anda."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Benarkan"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tolak"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Nyahpasang"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Apl menghalang permintaan kebenaran, maka jawapan anda tidak dapat disahkan."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ketik ciri untuk mula menggunakan ciri itu:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pilih ciri untuk digunakan dengan butang kebolehaksesan"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pilih ciri untuk digunakan dengan pintasan kekunci kelantangan"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hujung minggu"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Acara"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Tidur"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Hidup"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Mati"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> meredamkan sesetengah bunyi"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Terdapat masalah dalaman dengan peranti anda. Peranti mungkin tidak stabil sehingga anda membuat tetapan semula data kilang."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Terdapat masalah dalaman dengan peranti anda. Hubungi pengilang untuk mengetahui butirannya."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Peranti anda terlalu panas dan tidak dapat dicerminkan kepada paparan sehingga peranti sejuk"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fcc98d4..edbe31f 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"၎င်းသည် သင်နှင့် အက်ပ်တစ်ခု (သို့) အာရုံခံကိရိယာအကြား ပြန်လှန်တုံ့ပြန်မှုများကို မှတ်သားနိုင်ပြီး သင့်ကိုယ်စား အက်ပ်များနှင့် ပြန်လှန်တုံ့ပြန်နိုင်သည်။"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ခွင့်ပြုရန်"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ပယ်ရန်"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ဝန်ဆောင်မှုကို စတင်အသုံးပြုရန် တို့ပါ−"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"အများသုံးနိုင်မှု ခလုတ်ဖြင့် အသုံးပြုရန် ဝန်ဆောင်မှုများကို ရွေးပါ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"အသံခလုတ် ဖြတ်လမ်းလင့်ခ်ဖြင့် အသုံးပြုရန် ဝန်ဆောင်မှုများကို ရွေးပါ"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"စနေ၊ တနင်္ဂနွေ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"အစီအစဉ်"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"အိပ်နေချိန်"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ဖွင့်"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ပိတ်"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> သည် အချို့အသံကို ပိတ်နေသည်"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေပြီး၊ မူလစက်ရုံထုတ်အခြေအနေအဖြစ် ပြန်လည်ရယူနိုင်သည်အထိ အခြေအနေမတည်ငြိမ်နိုင်ပါ။"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေ၏။ အသေးစိတ်သိရန်အတွက် ပစ္စည်းထုတ်လုပ်သူအား ဆက်သွယ်ပါ။"</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"သင့်စက်ပစ္စည်း ပူလွန်းနေသဖြင့် ၎င်းမအေးသေးမီ ဖန်သားပြင်သို့ စကရင်ပွား၍မရပါ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 9d6e805..0732e67 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore kommunikasjonen din med en app eller maskinvaresensor og kommunisere med apper på dine vegne."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillat"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Avvis"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Avinstaller"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app dekker forespørselen om tillatelse, så svaret ditt kan ikke bekreftes."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Trykk på en funksjon for å begynne å bruke den:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Velg funksjonene du vil bruke med Tilgjengelighet-knappen"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Velg funksjonene du vil bruke med volumtastsnarveien"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Helg"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Aktivitet"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sover"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"På"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Av"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår av noen lyder"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Det har oppstått et internt problem på enheten din, og den kan være ustabil til du tilbakestiller den til fabrikkdata."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Det har oppstått et internt problem på enheten din. Ta kontakt med produsenten for mer informasjon."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Enheten er for varm og kan ikke speiles til skjermen før den kjøles ned"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index bd9853a..7ac4c14 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"यसले कुनै एप वा हार्डवेयर सेन्सरसँग तपाईंले गर्ने अन्तर्क्रिया ट्र्याक गर्न सक्छ र तपाईंका तर्फबाट एपहरूसँग अन्तर्क्रिया गर्न सक्छ।"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमति दिनुहोस्"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नदिनुहोस्"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइन्स्टल गर्नुहोस्"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"कुनै एपका कारण अनुमतिसम्बन्धी अनुरोध बुझ्न कठिनाइ भइरहेकाले तपाईंको जवाफको पुष्टि गर्न सकिएन।"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"कुनै सुविधा प्रयोग गर्न थाल्न उक्त सुविधामा ट्याप गर्नुहोस्:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"पहुँचको बटनमार्फत प्रयोग गर्न चाहेका सुविधाहरू छनौट गर्नुहोस्"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"भोल्युम कुञ्जीको सर्टकटमार्फत प्रयोग गर्न चाहेका सुविधाहरू छनौट गर्नुहोस्"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"शनिबार"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"कार्यक्रम"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"निदाएका बेला"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"अन छ"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"अफ छ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ले केही ध्वनिहरू म्युट गर्दै छ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ, र तपाईंले फ्याक्ट्री डाटा रिसेट नगर्दासम्म यो अस्थिर रहन्छ।"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ। विवरणहरूको लागि आफ्नो निर्मातासँग सम्पर्क गर्नुहोस्।"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"तपाईंको डिभाइस निकै तातो छ र यो चिसो नभएसम्म यसले डिस्प्लेमा मिरर गर्न सक्दैन"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a4bedd3..8f08689 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Deze functie kan je interacties met een app of een hardwaresensor bijhouden en namens jou met apps communiceren."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Toestaan"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Weigeren"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Verwijderen"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Een app dekt het verzoek om rechten af, waardoor je reactie niet kan worden geverifieerd."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tik op een functie om deze te gebruiken:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Functies kiezen voor gebruik met de knop Toegankelijkheid"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Functies kiezen voor gebruik met de sneltoets via de volumeknop"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Afspraken"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Slapen"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aan"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Uit"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> zet sommige geluiden uit"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Er is een intern probleem met je apparaat. Het apparaat kan instabiel zijn totdat u het apparaat terugzet naar de fabrieksinstellingen."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Er is een intern probleem met je apparaat. Neem contact op met de fabrikant voor meer informatie."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Je apparaat is te warm en kan pas naar het scherm mirroren als het is afgekoeld"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index af76df8..e91e005 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1689,8 +1689,8 @@
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ମାତ୍ରା ବଢ଼ାଇ ସୁପାରିଶ ସ୍ତର ବଢ଼ାଉଛନ୍ତି? \n\n ଲମ୍ବା ସମୟ ପର୍ଯ୍ୟନ୍ତ ଉଚ୍ଚ ଶବ୍ଦରେ ଶୁଣିଲେ ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତି ଖରାପ ହୋଇପାରେ।"</string>
-    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ଅଧିକ ଭଲ୍ୟୁମରେ ଶୁଣିବା ଜାରି ରଖିବେ?\n\nସୁପାରିଶ କରାଯାଇଥିବା ଅପେକ୍ଷା ଅଧିକ ସମୟ ପାଇଁ ହେଡଫୋନର ଭଲ୍ୟୁମ ଅଧିକ ଅଛି, ଯାହା ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତିକୁ ନଷ୍ଟ କରିପାରିବ"</string>
-    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ଉଚ୍ଚ ସାଉଣ୍ଡ ଚିହ୍ନଟ କରାଯାଇଛି\n\nହେଡଫୋନର ଭଲ୍ୟୁମ ସୁପାରିଶ କରାଯାଇଥିବା ଅପେକ୍ଷା ଅଧିକ ଅଛି, ଯାହା ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତିକୁ ନଷ୍ଟ କରିପାରିବ"</string>
+    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ଅଧିକ ଭଲ୍ୟୁମରେ ଶୁଣିବା ଜାରି ରଖିବେ?\n\nସୁପାରିଶ କରାଯାଇଥିବା ଭଲ୍ୟୁମ ଠାରୁ ହେଡଫୋନର ଭଲ୍ୟୁମ ଅଧିକ ଅଛି, ଯାହା ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତିକୁ ନଷ୍ଟ କରିପାରେ"</string>
+    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ଲାଉଡ ସାଉଣ୍ଡ ଚିହ୍ନଟ ହୋଇଛି\n\nସୁପାରିଶ ଭଲ୍ୟୁମ ଠାରୁ ହେଡଫୋନର ଭଲ୍ୟୁମ ଅଧିକ ଅଛି, ଯାହା ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତିକୁ ନଷ୍ଟ କରିପାରେ"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ଆକ୍ସେସବିଲିଟି ଶର୍ଟକଟ୍‍ ବ୍ୟବହାର କରିବେ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ସର୍ଟକଟ୍ ଚାଲୁ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍ ବଟନ୍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଏକ ଆକ୍ସେସବିଲିଟି ଫିଚର୍ ଆରମ୍ଭ ହେବ।"</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ପାଇଁ ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ଏହା କୌଣସି ଆପ କିମ୍ବା ହାର୍ଡୱେର ସେନ୍ସର ସହ ଆପଣଙ୍କର ଇଣ୍ଟେରାକ୍ସନକୁ ଟ୍ରାକ କରିପାରେ ଏବଂ ଆପଣଙ୍କ ତରଫରୁ ଆପ୍ସ ସହ ଇଣ୍ଟରାକ୍ଟ କରିପାରେ।"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ଅନୁମତି"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ଅଗ୍ରାହ୍ୟ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ଏକ ଆପ ଅନୁମତି ଅନୁରୋଧକୁ ଅସ୍ପଷ୍ଟ କରୁଛି ତେଣୁ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରାଯାଇପାରିବ ନାହିଁ।"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ଏକ ଫିଚର୍ ବ୍ୟବହାର କରିବା ଆରମ୍ଭ କରିବାକୁ ଏହାକୁ ଟାପ୍ କରନ୍ତୁ:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ଆକ୍ସେସିବିଲିଟୀ ବଟନ୍ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ଭଲ୍ୟୁମ୍ କୀ ସର୍ଟକଟ୍ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ସପ୍ତାହାନ୍ତ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ଇଭେଣ୍ଟ"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ଶୋଇବା"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ଚାଲୁ ଅଛି"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ବନ୍ଦ ଅଛି"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> କିଛି ସାଉଣ୍ଡକୁ ମ୍ୟୁଟ୍ କରୁଛି"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ଆପଣଙ୍କ ଡିଭାଇସ୍‍ରେ ଏକ ସମସ୍ୟା ରହିଛି ଏବଂ ଆପଣ ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଅସ୍ଥିର ରହିପାରେ।"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଏକ ସମସ୍ୟା ରହିଛି। ବିବରଣୀ ପାଇଁ ଆପଣଙ୍କ ଉତ୍ପାଦକଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ।"</string>
@@ -1943,7 +1947,7 @@
     <string name="notification_history_title_placeholder" msgid="7748630986182249599">"କଷ୍ଟମ୍ ଆପ୍ ବିଜ୍ଞପ୍ତି"</string>
     <string name="user_creation_account_exists" msgid="2239146360099708035">"<xliff:g id="APP">%1$s</xliff:g>ରେ ଏକ ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରିବା ପାଇଁ <xliff:g id="ACCOUNT">%2$s</xliff:g>କୁ (ପୂର୍ବରୁ ଏହି ଆକାଉଣ୍ଟ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ନାମରେ ଅଛି) ଅନୁମତି ଦେବେ?"</string>
     <string name="user_creation_adding" msgid="7305185499667958364">"<xliff:g id="APP">%1$s</xliff:g>ରେ ଏକ ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରିବା ପାଇଁ <xliff:g id="ACCOUNT">%2$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
-    <string name="supervised_user_creation_label" msgid="6884904353827427515">"ନିରୀକ୍ଷିତ ଉପଯୋଗକର୍ତ୍ତା ଯୋଗ କରନ୍ତୁ"</string>
+    <string name="supervised_user_creation_label" msgid="6884904353827427515">"ନିରୀକ୍ଷିତ ୟୁଜର ଯୋଗ କରନ୍ତୁ"</string>
     <string name="language_selection_title" msgid="52674936078683285">"ଏକ ଭାଷା ଯୋଗ କରନ୍ତୁ"</string>
     <string name="country_selection_title" msgid="5221495687299014379">"ପସନ୍ଦର ଅଞ୍ଚଳ"</string>
     <string name="search_language_hint" msgid="7004225294308793583">"ଭାଷାର ନାମ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ଆପଣଙ୍କ ଡିଭାଇସ ବହୁତ ଗରମ ଅଛି ଏବଂ ଏହା ଥଣ୍ଡା ନହେବା ପର୍ଯ୍ୟନ୍ତ ଡିସପ୍ଲେକୁ ମିରର କରିପାରିବ ନାହିଁ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 1db544a..29a0094 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1689,8 +1689,8 @@
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ਹਟਾਓ"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ਕੀ ਵੌਲਿਊਮ  ਸਿਫ਼ਾਰਸ਼  ਕੀਤੇ ਪੱਧਰ ਤੋਂ ਵਧਾਉਣੀ ਹੈ?\n\nਲੰਮੇ ਸਮੇਂ ਤੱਕ ਉੱਚ ਵੌਲਿਊਮ ਤੇ ਸੁਣਨ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ।"</string>
-    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ਕੀ ਉੱਚੀ ਅਵਾਜ਼ ਵਿੱਚ ਸੁਣਨਾ ਜਾਰੀ ਰੱਖਣਾ ਹੈ?\n\nਹੈੱਡਫ਼ੋਨ ਦੀ ਅਵਾਜ਼ ਸਿਫ਼ਾਰਸ਼ੀ ਸਮੇਂ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਦੇਰ ਤੱਕ ਉੱਚੀ ਰੱਖੀ ਗਈ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ"</string>
-    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ਉੱਚੀ ਧੁਨੀ ਦਾ ਪਤਾ ਲੱਗਾ\n\nਹੈੱਡਫ਼ੋਨ ਦੀ ਅਵਾਜ਼ ਨੂੰ ਸਿਫ਼ਾਰਸ਼ੀ ਪੱਧਰ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਦੇਰ ਤੱਕ ਉੱਚੀ ਰੱਖਿਆ ਗਿਆ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ"</string>
+    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"ਕੀ ਉੱਚੀ ਅਵਾਜ਼ ਵਿੱਚ ਸੁਣਦੇ ਰਹਿਣਾ ਹੈ?\n\nਹੈੱਡਫ਼ੋਨ ਦੀ ਅਵਾਜ਼ ਸਿਫ਼ਾਰਸ਼ੀ ਸਮੇਂ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਦੇਰ ਤੱਕ ਉੱਚੀ ਰੱਖੀ ਹੋਈ ਹੈ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ"</string>
+    <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"ਉੱਚੀ ਅਵਾਜ਼ ਦਾ ਪਤਾ ਲੱਗਾ\n\nਹੈੱਡਫ਼ੋਨ ਦੀ ਅਵਾਜ਼ ਸਿਫ਼ਾਰਸ਼ੀ ਪੱਧਰ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਦੇਰ ਤੱਕ ਉੱਚੀ ਰੱਖੀ ਹੋਈ ਹੈ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਸ਼ਾਰਟਕੱਟ ਵਰਤਣਾ ਹੈ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਲਈ ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ਇਹ ਕਿਸੇ ਐਪ ਜਾਂ ਹਾਰਡਵੇਅਰ ਸੈਂਸਰ ਦੇ ਨਾਲ ਤੁਹਾਡੀਆਂ ਅੰਤਰਕਿਰਿਆਵਾਂ ਨੂੰ ਟਰੈਕ ਕਰ ਸਕਦੀ ਹੈ, ਅਤੇ ਤੁਹਾਡੀ ਤਰਫ਼ੋਂ ਐਪਾਂ ਦੇ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰ ਸਕਦੀ ਹੈ।"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ਕਰਨ ਦਿਓ"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ਨਾ ਕਰਨ ਦਿਓ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ਕੋਈ ਐਪ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਬੇਨਤੀ ਨੂੰ ਅਸਪਸ਼ਟ ਕਰ ਰਹੀ ਹੈ, ਇਸ ਲਈ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ਕਿਸੇ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਵਰਤਣਾ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਉਸ \'ਤੇ ਟੈਪ ਕਰੋ:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ਪਹੁੰਚਯੋਗਤਾ ਬਟਨ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ਅਵਾਜ਼ ਕੁੰਜੀ ਸ਼ਾਰਟਕੱਟ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ਹਫ਼ਤੇ ਦਾ ਅੰਤਲਾ ਦਿਨ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ਇਵੈਂਟ"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ਸੌਣ ਵੇਲੇ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ਚਾਲੂ ਹੈ"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ਬੰਦ ਹੈ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ਕੁਝ ਧੁਨੀਆਂ ਨੂੰ ਮਿਊਟ ਕਰ ਰਹੀ ਹੈ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਹੈ ਅਤੇ ਇਹ ਅਸਥਿਰ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਨਹੀਂ ਕਰਦੇ।"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਸੀ। ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਨਿਰਮਾਤਾ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਰਮ ਹੈ ਅਤੇ ਜਦੋਂ ਤੱਕ ਇਹ ਠੰਡਾ ਨਹੀਂ ਹੋ ਜਾਂਦਾ ਉਦੋਂ ਤੱਕ ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index f708f7d..208810d 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Może śledzić Twoje interakcje z aplikacjami lub czujnikiem sprzętowym, a także obsługiwać aplikacje za Ciebie."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Zezwól"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odmów"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odinstaluj"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacja zasłania prośbę o uprawnienia, więc nie można zweryfikować Twojej odpowiedzi."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Wybierz funkcję, aby zacząć z niej korzystać:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Wybierz funkcje, których chcesz używać z przyciskiem ułatwień dostępu"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Wybierz funkcje, do których chcesz używać skrótu z klawiszami głośności"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Wydarzenie"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sen"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Włączono"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Wyłączono"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> wycisza niektóre dźwięki"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"W Twoim urządzeniu wystąpił problem wewnętrzny. Może być ono niestabilne, dopóki nie przywrócisz danych fabrycznych."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"W Twoim urządzeniu wystąpił problem wewnętrzny. Skontaktuj się z jego producentem, by otrzymać szczegółowe informacje."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Urządzenie ma zbyt wysoką temperaturę i nie może utworzyć odbicia lustrzanego zawartości ekranu, dopóki się nie ochłodzi."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index b3b0e26..c509ed5 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorar suas interações com um app ou um sensor de hardware e interagir com apps em seu nome."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Negar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque em um recurso para começar a usá-lo:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha recursos para usar com o botão de acessibilidade"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha recursos para usar com o atalho da tecla de volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Há um problema interno com seu dispositivo. Entre em contato com o fabricante para saber mais detalhes."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está muito quente. Não será possível espelhar a tela até ele resfriar"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 784f2a9..daa2504 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorizar as suas interações com uma app ou um sensor de hardware e interagir com apps em seu nome."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Recusar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Uma app está a ocultar o pedido de autorização e, por isso, não é possível validar a sua resposta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque numa funcionalidade para começar a utilizá-la:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha funcionalidades para utilizar com o botão Acessibilidade"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha funcionalidades para usar com o atalho das teclas de volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"A dormir"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Existe um problema interno no seu dispositivo. Contacte o fabricante para obter mais informações."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está demasiado quente e não consegue espelhar para o ecrã até arrefecer"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index b3b0e26..c509ed5 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorar suas interações com um app ou um sensor de hardware e interagir com apps em seu nome."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Negar"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque em um recurso para começar a usá-lo:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha recursos para usar com o botão de acessibilidade"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha recursos para usar com o atalho da tecla de volume"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Há um problema interno com seu dispositivo. Entre em contato com o fabricante para saber mais detalhes."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está muito quente. Não será possível espelhar a tela até ele resfriar"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 88f5044..562fcc9 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Poate să urmărească interacțiunile tale cu o aplicație sau cu un senzor hardware și să interacționeze cu aplicații în numele tău."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permite"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuz"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Dezinstalează"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"O aplicație blochează solicitarea de permisiune, așa că răspunsul nu se poate verifica."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Atinge o funcție ca să începi să o folosești:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Alege funcțiile pe care să le folosești cu butonul de accesibilitate"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Alege funcțiile pentru comanda rapidă a butonului de volum"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Eveniment"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Somn"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activată"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Dezactivată"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactează producătorul."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Dispozitivul este prea cald și nu poate oglindi ecranul până când nu se răcește"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 7f5e87f..84a4776 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Сервис может отслеживать ваше взаимодействие с приложениями и датчиками устройства и давать приложениям команды от вашего имени."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Разрешить"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Отклонить"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Удалить"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Невозможно принять ваш ответ, поскольку запрос разрешения скрыт другим приложением."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Выберите, какую функцию использовать:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Выберите функции, которые будут запускаться с помощью кнопки специальных возможностей"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберите функции, которые будут запускаться с помощью кнопки регулировки громкости"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Выходные"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Мероприятие"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Время сна"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Включено"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Отключено"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> приглушает некоторые звуки."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Произошла внутренняя ошибка, и устройство может работать нестабильно, пока вы не выполните сброс настроек."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Произошла внутренняя ошибка. Обратитесь к производителю устройства за подробными сведениями."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Ваше устройство слишком сильно нагрелось. Когда оно остынет, вы снова сможете передавать изображение на другой экран."</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index e90f1a2..c54e768 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"මෙයට යෙදුමක් හෝ දෘඪාංග සංවේදකයක් සමඟ ඔබේ අන්තර්ක්‍රියා හඹා යෑමට, සහ ඔබ වෙනුවෙන් යෙදුම් සමඟ අන්තර්ක්‍රියාවේ යෙදීමට හැකි ය."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ඉඩ දෙන්න"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ප්‍රතික්‍ෂේප කරන්න"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"එය භාවිත කිරීම ආරම්භ කිරීමට විශේෂාංගයක් තට්ටු කරන්න:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ප්‍රවේශ්‍යතා බොත්තම සමග භාවිත කිරීමට විශේෂාංග තෝරා ගන්න"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"හඬ පරිමා යතුරු කෙටිමග සමග භාවිත කිරීමට විශේෂාංග තෝරා ගන්න"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"සති අන්තය"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"සිදුවීම"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"නිදා ගනිමින්"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ක්‍රියාත්මකයි"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ක්‍රියාවිරහිතයි"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> සමහර ශබ්ද නිහඬ කරමින්"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ඔබේ උපාංගය සමගින් ගැටලුවක් ඇති අතර, ඔබේ කර්මාන්තශාලා දත්ත යළි සකසන තෙක් එය අස්ථායි විය හැකිය."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"ඔබේ උපාංගය සමගින් අභ්‍යන්තර ගැටලුවක් ඇත. විස්තර සඳහා ඔබේ නිෂ්පාදක අමතන්න."</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්‍රෆෝනය අවහිර කර ඇත"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ඔබේ උපාංගය ඉතා උණුසුම් වන අතර එය සිසිල් වන තෙක් සංදර්ශකය වෙත පිළිබිඹු කළ නොහැක"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 3da576c..f2153231 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Môže sledovať vaše interakcie s aplikáciou alebo hardvérovým senzorom a interagovať s aplikáciami za vás."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Povoliť"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zamietnuť"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odinštalovať"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikácia zakrýva žiadosť o povolenie, takže vaša odpoveď sa nedá overiť."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Klepnutím na funkciu ju začnite používať:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Výber funkcií, ktoré chcete používať tlačidlom dostupnosti"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Výber funkcií, ktoré chcete používať klávesovou skratkou"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Víkend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Udalosť"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spánok"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Zapnuté"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Vypnuté"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypína niektoré zvuky"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Vo vašom zariadení došlo k internému problému. Môže byť nestabilné, kým neobnovíte jeho výrobné nastavenia."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Vo vašom zariadení došlo k internému problému. Ak chcete získať podrobné informácie, obráťte sa na jeho výrobcu."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Zariadenie je príliš horúce a nemôže zrkadliť na obrazovku, kým sa neochladí"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 5b731b7..e8ba9dd 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1712,6 +1712,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Spremlja lahko vaše interakcije z aplikacijo ali tipalom strojne opreme ter komunicira z aplikacijami v vašem imenu."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dovoli"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zavrni"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odmesti"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija zakriva zahtevo za dovoljenje, zato ni mogoče potrditi vašega odgovora."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Če želite začeti uporabljati funkcijo, se je dotaknite:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Izberite funkcije, ki jih želite uporabljati z gumbom za dostopnost"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Izberite funkcije, ki jih želite uporabljati z bližnjico na tipki za glasnost"</string>
@@ -1906,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Konec tedna"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Dogodek"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spanje"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Vklopljeno"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Izklopljeno"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izklaplja nekatere zvoke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Vaša naprava ima notranjo napako in bo morda nestabilna, dokler je ne ponastavite na tovarniške nastavitve."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Vaša naprava ima notranjo napako. Če želite več informacij, se obrnite na proizvajalca."</string>
@@ -2338,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Naprava je pretopla in ne more zrcaliti v zaslon, dokler se ne ohladi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index a4fd1bf1..1d28440 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Mund të monitorojë ndërveprimet me një aplikacion ose një sensor hardueri dhe të ndërveprojë me aplikacionet në emrin tënd."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Lejo"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuzo"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Trokit te një veçori për të filluar ta përdorësh atë:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Zgjidh veçoritë që do të përdorësh me butonin e qasshmërisë"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Zgjidh veçoritë që do të përdorësh me shkurtoren e tastit të volumit"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fundjava"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Ngjarje"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Në gjumë"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktivizuar"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Çaktivizuar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> po çaktivizon disa tinguj"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ka një problem të brendshëm me pajisjen tënde. Ajo mund të jetë e paqëndrueshme derisa të rivendosësh të dhënat në gjendje fabrike."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Ka një problem të brendshëm me pajisjen tënde. Kontakto prodhuesin tënd për detaje."</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Pajisja është shumë e nxehtë dhe nuk mund të pasqyrojë në ekran derisa të ftohet"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index e5ae2f7..a6c994e 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1711,6 +1711,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Може да прати интеракције са апликацијом или сензором хардвера и користи апликације уместо вас."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дозволи"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Одбиј"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Деинсталирај"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Апликација крије захтев за дозволу, па одговор не може да се верификује."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Додирните неку функцију да бисте почели да је користите:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Одаберите функције које ћете користити са дугметом Приступачност"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Одаберите функције за пречицу тастером јачине звука"</string>
@@ -1905,6 +1907,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Викенд"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Догађај"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Спавање"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Укључено"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Искључено"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> искључује неке звуке"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Дошло је до интерног проблема у вези са уређајем и можда ће бити нестабилан док не обавите ресетовање на фабричка подешавања."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Дошло је до интерног проблема у вези са уређајем. Потражите детаље од произвођача."</string>
@@ -2337,6 +2341,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Уређај је превише загрејан, па не може да се пресликава на екран док се не охлади"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index f3917fe..15b7029 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan registrera din användning av en app eller maskinvarusensor och interagera med appar åt dig."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillåt"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Neka"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Avinstallera"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app döljer behörighetsbegäran så det går inte att verifiera svaret."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tryck på funktioner som du vill aktivera:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Välj vilka funktioner du vill använda med hjälp av tillgänglighetsknappen"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Välj att funktioner att använda med hjälp av volymknappskortkommandot"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"I helgen"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Händelse"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"När jag sover"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"På"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Av"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> stänger av vissa ljud"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ett internt problem har uppstått i enheten, och det kan hända att problemet kvarstår tills du återställer standardinställningarna."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Ett internt problem har uppstått i enheten. Kontakta tillverkaren om du vill veta mer."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Enheten är för varm för att spegla skärmen. Vänta tills den har svalnat"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 022331c..aef89ee 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Inaweza kufuatilia mawasiliano yako na programu au kitambuzi cha maunzi na kuwasiliana na programu zingine kwa niaba yako."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ruhusu"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Kataa"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Ondoa"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Programu inazuia ombi la ruhusa kwa hivyo jibu lako haliwezi kuthibitishwa."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Gusa kipengele ili uanze kukitumia:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Chagua vipengele vya kutumia na kitufe cha zana za ufikivu"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Chagua vipengele vya kutumia na njia ya mkato ya kitufe cha sauti"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Wikendi"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tukio"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Kulala"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Imewashwa"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Imezimwa"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> inazima baadhi ya sauti"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Kuna hitilafu ya ndani ya kifaa chako, na huenda kisiwe thabiti mpaka urejeshe mipangilio ya kiwandani."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Kuna hitilafu ya ndani ya kifaa chako. Wasiliana na mtengenezaji wa kifaa chako kwa maelezo."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Kifaa chako kina joto sana, hakiwezi kuakisi skrini hadi joto lake lipungue"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 9ad4bd2..f81e935 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ஏதேனும் ஆப்ஸ் அல்லது வன்பொருள் சென்சாரின் உதவியுடன் உரையாடல்களைக் கண்காணித்து உங்கள் சார்பாக ஆப்ஸுடன் உரையாட இச்சேவையால் இயலும்."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"அனுமதி"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"நிராகரி"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"நிறுவல் நீக்கும்"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"அணுகல் கோரிக்கையை ஓர் ஆப்ஸ் மறைப்பதால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ஒரு அம்சத்தைப் பயன்படுத்த அதைத் தட்டவும்:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"அணுகல்தன்மை பட்டன் மூலம் பயன்படுத்த விரும்பும் அம்சங்களைத் தேர்வுசெய்யுங்கள்"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ஒலியளவு விசை ஷார்ட்கட் மூலம் பயன்படுத்த விரும்பும் அம்சங்களைத் தேர்வுசெய்யுங்கள்"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"வார இறுதி"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"நிகழ்வு"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"உறக்கத்தில்"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ஆன்"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ஆஃப்"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> சில ஒலிகளை முடக்குகிறது"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது, அதனை ஆரம்பநிலைக்கு மீட்டமைக்கும் வரை நிலையற்று இயங்கலாம்."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது. விவரங்களுக்கு சாதன தயாரிப்பாளரைத் தொடர்புகொள்ளவும்."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"உங்கள் சாதனம் மிகவும் சூடாக இருப்பதால், அது குறையும் வரை காட்சியைப் பிரதிபலிக்க முடியாது"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index d1c336d..671aefb 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"మీరు ఒక యాప్‌‌తో చేసే ఇంటరాక్షన్‌లను లేదా హార్డ్‌వేర్ సెన్సార్‌ను ట్రాక్ చేస్తూ మీ త‌ర‌ఫున యాప్‌లతో ఇంటరాక్ట్ చేయగలదు."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"అనుమతించండి"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"వద్దు"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"అన్ఇన్‌స్టాల్ చేయండి"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ఒక యాప్ అనుమతి రిక్వెస్ట్‌కు అడ్డు తగులుతోంది కాబట్టి మీ సమాధానం వెరిఫై చేయడం సాధ్యం కాదు."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ఫీచర్‌ని ఉపయోగించడం ప్రారంభించడానికి, దాన్ని ట్యాప్ చేయండి:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"యాక్సెసిబిలిటీ బటన్‌తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"వాల్యూమ్ కీ షార్ట్‌కట్‌తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"వారాంతం"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ఈవెంట్"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"నిద్రావస్థ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ఆన్‌లో ఉంది"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ఆఫ్‌లో ఉంది"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> కొన్ని ధ్వనులను మ్యూట్ చేస్తోంది"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది మరియు మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసే వరకు అస్థిరంగా ఉంటుంది."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది. వివరాల కోసం మీ తయారీదారుని సంప్రదించండి."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్‌ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్‌ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"మీ పరికరం చాలా వెచ్చగా ఉంది, అది చల్లబడే వరకు డిస్‌ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్‌ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్‌ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 06920b0..2d5ddf4 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"การควบคุมนี้สามารถติดตามการโต้ตอบของคุณกับแอปหรือกับเซ็นเซอร์ของฮาร์ดแวร์ และโต้ตอบกับแอปต่างๆ แทนคุณ"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"อนุญาต"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ปฏิเสธ"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ถอนการติดตั้ง"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"มีแอปหนึ่งบดบังคำขอสิทธิ์ เราจึงยืนยันการตอบกลับของคุณไม่ได้"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"แตะฟีเจอร์เพื่อเริ่มใช้"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"เลือกฟีเจอร์ที่จะใช้กับปุ่มการช่วยเหลือพิเศษ"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"เลือกฟีเจอร์ที่จะใช้กับทางลัดปุ่มปรับระดับเสียง"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"สุดสัปดาห์"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"กิจกรรม"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"นอนหลับ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"เปิด"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ปิด"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> กำลังปิดเสียงบางรายการ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง อุปกรณ์อาจทำงานไม่เสถียรจนกว่าคุณจะรีเซ็ตข้อมูลเป็นค่าเริ่มต้น"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง โปรดติดต่อผู้ผลิตเพื่อขอรายละเอียดเพิ่มเติม"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"อุปกรณ์ร้อนเกินไปและไม่สามารถมิเรอร์ไปยังจอแสดงผลได้จนกว่าจะเย็นลง"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 2477698..47c4b5e 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Masusubaybayan nito ang iyong mga pakikipag-ugayan sa isang app o hardware na sensor, at puwede itong makipag-ugnayan sa mga app para sa iyo."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Payagan"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tanggihan"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"I-uninstall"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"May app na pumipigil sa kahilingan sa pahintulot kaya hindi ma-verify ang iyong sagot."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"I-tap ang isang feature para simulan itong gamitin:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pumili ng mga feature na gagana sa pamamagitan ng button ng accessibility"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pumili ng mga feature na gagana sa pamamagitan ng shortcut ng volume key"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Pag-sleep"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Naka-on"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Naka-off"</string>
     <string name="muted_by" msgid="91464083490094950">"Minu-mute ng <xliff:g id="THIRD_PARTY">%1$s</xliff:g> ang ilang tunog"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"May internal na problema sa iyong device, at maaaring hindi ito maging stable hanggang sa i-reset mo ang factory data."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"May internal na problema sa iyong device. Makipag-ugnayan sa iyong manufacturer upang malaman ang mga detalye."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Masyadong mainit ang iyong device at hindi ito makakapag-mirror sa display hanggang sa lumamig ito"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5598dbc..03536fe 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Bir uygulama veya donanım sensörüyle etkileşimlerinizi takip edebilir ve sizin adınıza uygulamalarla etkileşimde bulunabilir."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"İzin ver"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Reddet"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Kaldır"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Bir uygulama, izin isteğini gizlediğinden yanıtınız doğrulanamıyor."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Kullanmaya başlamak için bir özelliğe dokunun:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Erişilebilirlik düğmesiyle kullanılacak özellikleri seçin"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Ses tuşu kısayoluyla kullanılacak özellikleri seçin"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hafta sonu"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Etkinlik"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Uyku"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Açık"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kapalı"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bazı sesleri kapatıyor"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızla ilgili dahili bir sorun oluştu ve fabrika verilerine sıfırlama işlemi gerçekleştirilene kadar kararsız çalışabilir."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Cihazınızla ilgili dahili bir sorun oluştu. Ayrıntılı bilgi için üreticinizle iletişim kurun."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Cihazınız çok ısındığı için soğuyana kadar ekrana yansıtılamaz"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d4cd207..24cbc3f 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1712,6 +1712,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Цей сервіс може відстежувати вашу взаємодію з додатком чи апаратним датчиком, а також взаємодіяти з додатками від вашого імені."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дозволити"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Заборонити"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Натисніть функцію, щоб почати використовувати її:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Виберіть функції для кнопки спеціальних можливостей"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Виберіть функції для комбінації з клавішами гучності"</string>
@@ -1906,6 +1910,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"На вихідних"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Подія"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Під час сну"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Увімкнено"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Вимкнено"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> вимикає деякі звуки"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Через внутрішню помилку ваш пристрій може працювати нестабільно. Відновіть заводські налаштування."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"На пристрої сталася внутрішня помилка. Зв’яжіться з виробником пристрою, щоб дізнатися більше."</string>
@@ -2338,6 +2344,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Пристрій перегрівся й не зможе дублювати контент на дисплей, поки не охолоне"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 8634294..728b5ee 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"یہ کسی ایپ یا ہارڈویئر سینسر کے ساتھ آپ کے تعاملات کو ٹریک کر سکتا ہے، اور آپ کی طرف سے ایپس کے ساتھ تعامل کر سکتا ہے۔"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازت دیں"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مسترد کریں"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ایک خصوصیت کا استعمال شروع کرنے کیلئے اسے تھپتھپائیں:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ایکسیسبیلٹی بٹن کے ساتھ استعمال کرنے کیلئے خصوصیات منتخب کریں"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"والیوم کلید کے شارٹ کٹ کے ساتھ استعمال کرنے کیلئے خصوصیات منتخب کریں"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ویک اینڈ"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ایونٹ"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"سونا"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"آن ہے"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"آف ہے"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> کچھ آوازوں کو خاموش کر رہا ہے"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"آپ کے آلہ میں ایک داخلی مسئلہ ہے اور جب تک آپ فیکٹری ڈیٹا کو دوبارہ ترتیب نہیں دے دیتے ہیں، ہوسکتا ہے کہ یہ غیر مستحکم رہے۔"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"آپ کے آلہ میں ایک داخلی مسئلہ ہے۔ تفصیلات کیلئے اپنے مینوفیکچرر سے رابطہ کریں۔"</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"آپ کا آلہ بہت گرم ہے اور جب تک یہ ٹھنڈا نہیں ہو جاتا اس وقت تک ڈسپلے میں عکس دو طرفہ مطابقت پذیر نہیں ہو سکتا"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index fdea194..1e1807e 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ilova yoki qurilma sensori bilan munosabatlaringizni kuzatishi hamda sizning nomingizdan ilovalar bilan ishlashi mumkin."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ruxsat"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Rad etish"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Kerakli funksiyani tanlang"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Qulayliklar tugmasi bilan foydalanish uchun funksiyalarni tanlang"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Tovush tugmasi bilan ishga tushiriladigan funksiyalarni tanlang"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Dam olish kunlari"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tadbir"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Uyquda"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Yoniq"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Oʻchiq"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ayrim tovushlarni ovozsiz qilgan"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. U zavod sozlamalari tiklanmaguncha barqaror ishlamasligi mumkin."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. Tafsilotlar uchun qurilmangiz ishlab chiqaruvchisiga murojaat qiling."</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Qurilma qizib ketdi va u sovimaguncha displeyni aks ettirmaydi"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 12a61ed..94ebd97 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Dịch vụ này có thể theo dõi các hoạt động tương tác của bạn với một ứng dụng hoặc bộ cảm biến phần cứng, cũng như có thể thay mặt bạn tương tác với các ứng dụng."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Cho phép"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Từ chối"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Gỡ cài đặt"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Một ứng dụng đang che khuất yêu cầu quyền này nên chúng tôi không thể xác minh phản hồi của bạn."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Nhấn vào một tính năng để bắt đầu sử dụng:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Chọn các tính năng để dùng với nút hỗ trợ tiếp cận"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Chọn các tính năng để dùng với phím tắt là phím âm lượng"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Cuối tuần"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Sự kiện"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ngủ"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Bật"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Tắt"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> đang tắt một số âm thanh"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Đã xảy ra sự cố nội bộ với thiết bị của bạn và thiết bị có thể sẽ không ổn định cho tới khi bạn thiết lập lại dữ liệu ban đầu."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Đã xảy ra sự cố nội bộ với thiết bị. Hãy liên hệ với nhà sản xuất của bạn để biết chi tiết."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Thiết bị của bạn quá nóng nên không phản chiếu được nội dung lên màn hình. Hãy để thiết bị nguội bớt"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 2ae8fe6..108f507 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"此功能可以跟踪您与应用或硬件传感器的互动,并代表您与应用互动。"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"允许"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"拒绝"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"卸载"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"应用遮挡了权限请求,因此我们无法验证您的回复。"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"点按相应功能即可开始使用:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"选择可通过“无障碍”按钮使用的功能"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"选择可通过音量键快捷方式使用的功能"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"周末"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活动"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已启用"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已停用"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正在将某些音效设为静音"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"您的设备内部出现了问题。如果不将设备恢复出厂设置,设备运行可能会不稳定。"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"您的设备内部出现了问题。请联系您的设备制造商了解详情。"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"设备温度过高,在温度降下来之前,设备无法镜像到显示屏"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index e108238..a37f9a8 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"這項功能會追蹤你與應用程式或硬件感應器的互動,並代表你直接與應用程式互動。"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"允許"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"拒絕"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"解除安裝"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"有應用程式阻擋權限要求,因此系統無法驗證你的回應。"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"輕按即可開始使用所需功能:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"選擇要配搭無障礙功能按鈕使用的功能"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"選擇要用音量快速鍵的功能"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活動"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已開啟"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已關閉"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正將某些音效設為靜音"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"你裝置的系統發生問題,回復原廠設定後即可解決該問題。"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"你裝置的系統發生問題,請聯絡你的製造商瞭解詳情。"</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"裝置過熱,請等到降溫後再將畫面鏡像投射至螢幕"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 5b65201..5dced74 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1710,6 +1710,10 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"可追蹤你與應用程式或硬體感應器的互動,並代表你與應用程式進行互動。"</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"允許"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"拒絕"</string>
+    <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) -->
+    <skip />
+    <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) -->
+    <skip />
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"輕觸即可開始使用所需功能:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"選擇要搭配無障礙工具按鈕使用的功能"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"選擇要搭配音量快速鍵使用的功能"</string>
@@ -1904,6 +1908,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活動"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已啟用"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已停用"</string>
     <string name="muted_by" msgid="91464083490094950">"「<xliff:g id="THIRD_PARTY">%1$s</xliff:g>」正在關閉部分音效"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"你的裝置發生內部問題,必須將裝置恢復原廠設定才能解除不穩定狀態。"</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"你的裝置發生內部問題,詳情請洽裝置製造商。"</string>
@@ -2336,6 +2342,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"麥克風已封鎖"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他傳輸線,然後再試一次"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"裝置過熱,請等到降溫後再將畫面鏡像投放至螢幕"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"傳輸線可能不支援螢幕"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C 傳輸線可能未妥善連接到螢幕"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index b701abe..aaa1787 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1710,6 +1710,8 @@
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ingalandela ukusebenzisana kwakho nohlelo lokusebenza noma inzwa yehadiwe, nokusebenzisana nezinhlelo zokusebenza engxenyeni yakho."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Vumela"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Phika"</string>
+    <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Khipha"</string>
+    <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"I-app ifihla isicelo semvume ngakho impendulo yakho ayikwazi ukuqinisekiswa."</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Thepha isici ukuqala ukusisebenzisa:"</string>
     <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Khetha izici ongazisebenzisa nenkinobho yokufinyeleleka"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Khetha izici ongazisebenzisa nesinqamuleli sokhiye wevolumu"</string>
@@ -1904,6 +1906,8 @@
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Ngempelasonto"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Umcimbi"</string>
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ulele"</string>
+    <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Kuvuliwe"</string>
+    <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kuvaliwe"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ithulisa eminye imisindo"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Kukhona inkinga yangaphakathi ngedivayisi yakho, futhi ingase ibe engazinzile kuze kube yilapho usetha kabusha yonke idatha."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Kukhona inkinga yangaphakathi ngedivayisi yakho. Xhumana nomkhiqizi wakho ukuze uthole imininingwane."</string>
@@ -2336,6 +2340,7 @@
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string>
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Idivayisi yakho ifudumele kakhulu futhi ayikwazi ukwenza isibuko kusibonisi size siphole"</string>
     <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string>
     <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9f99dc9..f8546b7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1592,6 +1592,12 @@
     <!-- Whether attributions provided are meant to be user-visible. -->
     <attr name="attributionsAreUserVisible" format="boolean" />
 
+    <!-- If a preloaded APK is marked updatableSystem = false, any request for an update will be rejected.
+         If an APK marked updatableSystem = false is being installed, regardless of the updatableSystem state
+         of the version it's replacing, the install will be rejected.
+         This is a private attribute, used without android: namespace. -->
+    <attr name="updatableSystem" format="boolean" />
+
     <!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags
          together. -->
     <attr name="foregroundServiceType">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f827d28..8bbc809 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1522,6 +1522,10 @@
     config_screenBrightnessSettingDefaultFloat instead -->
     <integer name="config_screenBrightnessSettingDefault">102</integer>
 
+    <!-- Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+    The value must be in the range [0, 255]. -->
+    <integer name="config_screenBrightnessCapForWearBedtimeMode">20</integer>
+
     <!-- Minimum screen brightness setting allowed by power manager.
          -2 is invalid so setting will resort to int value specified above.
          Set this to 0.0 to allow screen to go to minimal brightness.
@@ -4336,6 +4340,9 @@
     <!-- True if assistant app should be pinned via Pinner Service -->
     <bool name="config_pinnerAssistantApp">false</bool>
 
+    <!-- Bytes that the PinnerService will pin for WebView -->
+    <integer name="config_pinnerWebviewPinBytes">0</integer>
+
     <!-- Number of days preloaded file cache should be preserved on a device before it can be
          deleted -->
     <integer name="config_keepPreloadsMinDays">7</integer>
@@ -5465,6 +5472,7 @@
         <item>1,1,1.0,.15,15</item>
         <item>0,0,0.7,0,1</item>
         <item>0,0,0.83333,0,1</item>
+        <item>0,0,1.1667,0,1</item>
     </string-array>
 
     <!-- The integer index of the selected option in config_udfps_touch_detection_options -->
@@ -6806,6 +6814,10 @@
     <!-- Whether or not ActivityManager PSS profiling is disabled. -->
     <bool name="config_am_disablePssProfiling">false</bool>
 
+    <!-- The modifier used to adjust AM's PSS threshold for downgrading services to service B if
+         RSS is being collected instead. -->
+    <item name="config_am_pssToRssThresholdModifier" format="float" type="dimen">1.5</item>
+
     <!-- Whether unlocking and waking a device are sequenced -->
     <bool name="config_orderUnlockAndWake">false</bool>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 39d958c..8d80af4 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -243,10 +243,6 @@
     <bool name="allow_clear_initial_attach_data_profile">false</bool>
     <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
 
-    <!-- Boolean indicating whether TelephonyAnalytics module is active or not. -->
-    <bool name="telephony_analytics_switch">true</bool>
-    <java-symbol type="bool" name="telephony_analytics_switch" />
-
     <!-- Whether to enable modem on boot if behavior is not defined -->
     <bool name="config_enable_cellular_on_boot_default">true</bool>
     <java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index eed186a..73a7e42 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6350,4 +6350,20 @@
     <string name="keyboard_layout_notification_multiple_selected_title">Physical keyboards configured</string>
     <!-- Notification message when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_BODY] -->
     <string name="keyboard_layout_notification_multiple_selected_message">Tap to view keyboards</string>
+
+    <!-- Private profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_private">Private</string>
+    <!-- Clone profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_clone">Clone</string>
+    <!-- Work profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_work">Work</string>
+    <!-- 2nd Work profile label on a screen in case a device has more than one work profiles. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_work_2">Work 2</string>
+    <!-- 3rd Work profile label on a screen in case a device has more than two work profiles. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_work_3">Work 3</string>
+    <!-- Test profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20]  -->
+    <string name="profile_label_test">Test</string>
+    <!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+    <string name="profile_label_communal">Communal</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f3aa936..3d43004 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1086,6 +1086,13 @@
   <java-symbol type="string" name="managed_profile_label_badge_3" />
   <java-symbol type="string" name="clone_profile_label_badge" />
   <java-symbol type="string" name="private_profile_label_badge" />
+  <java-symbol type="string" name="profile_label_private" />
+  <java-symbol type="string" name="profile_label_clone" />
+  <java-symbol type="string" name="profile_label_work" />
+  <java-symbol type="string" name="profile_label_work_2" />
+  <java-symbol type="string" name="profile_label_work_3" />
+  <java-symbol type="string" name="profile_label_test" />
+  <java-symbol type="string" name="profile_label_communal" />
   <java-symbol type="string" name="mediasize_unknown_portrait" />
   <java-symbol type="string" name="mediasize_unknown_landscape" />
   <java-symbol type="string" name="mediasize_iso_a0" />
@@ -2071,6 +2078,7 @@
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
+  <java-symbol type="integer" name="config_screenBrightnessCapForWearBedtimeMode" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMinimumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMaximumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingDefaultFloat" />
@@ -3378,6 +3386,7 @@
   <java-symbol type="bool" name="config_pinnerCameraApp" />
   <java-symbol type="bool" name="config_pinnerHomeApp" />
   <java-symbol type="bool" name="config_pinnerAssistantApp" />
+  <java-symbol type="integer" name="config_pinnerWebviewPinBytes" />
 
   <java-symbol type="string" name="config_doubleTouchGestureEnableFile" />
 
@@ -5270,6 +5279,7 @@
 
   <!-- For ActivityManager PSS profiling configurability -->
   <java-symbol type="bool" name="config_am_disablePssProfiling" />
+  <java-symbol type="dimen" name="config_am_pssToRssThresholdModifier" />
 
   <java-symbol type="raw" name="default_ringtone_vibration_effect" />
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index d4a88c4..a3a1d3a 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -34,9 +34,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
-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.util.ArraySet;
 
 import com.google.common.truth.Expect;
@@ -150,7 +148,7 @@
     @Rule
     public final Expect mExpect = Expect.create();
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
     public void getIdentifierTypes_forFilter() {
@@ -631,8 +629,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getProgramInfos() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         createRadioTuner();
         mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
         registerListCallbacks(/* numCallbacks= */ 1);
@@ -648,8 +646,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getProgramInfos_withIdNotFound() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         createRadioTuner();
         mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
         registerListCallbacks(/* numCallbacks= */ 1);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index 03de143..89464d1 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -32,9 +32,7 @@
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.RemoteException;
-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.util.ArrayMap;
 
 import org.junit.Rule;
@@ -168,7 +166,7 @@
     private ICloseHandle mCloseHandleMock;
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
     public void getType_forBandDescriptor() {
@@ -962,22 +960,25 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isSignalAcquired_forProgramInfo() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         assertWithMessage("Signal acquisition status for HD program info")
                 .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue();
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isHdSisAvailable_forProgramInfo() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         assertWithMessage("SIS information acquisition status for HD program")
                 .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue();
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isHdAudioAvailable_forProgramInfo() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         assertWithMessage("Audio acquisition status for HD program")
                 .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse();
     }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 3891acc..7b9121e 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -22,10 +22,7 @@
 
 import android.graphics.Bitmap;
 import android.os.Parcel;
-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 org.junit.Rule;
 import org.junit.Test;
@@ -52,7 +49,7 @@
     private Bitmap mBitmapValue;
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
     public void describeContents_forClock() {
@@ -128,8 +125,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void putStringArray_withIllegalKey_throwsException() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
 
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
@@ -142,8 +139,9 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void putStringArray_withNullKey_throwsException() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
             mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
         });
@@ -153,8 +151,9 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void putStringArray_withNullString_throwsException() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
             mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
         });
@@ -281,8 +280,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getStringArray_withKeyInMetadata() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String key = RadioMetadata.METADATA_KEY_UFIDS;
         RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
 
@@ -291,8 +290,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getStringArray_withKeyNotInMetadata() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String key = RadioMetadata.METADATA_KEY_UFIDS;
         RadioMetadata metadata = mBuilder.build();
 
@@ -305,8 +304,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getStringArray_withNullKey() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         RadioMetadata metadata = mBuilder.build();
 
         NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
@@ -318,8 +317,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void getStringArray_withInvalidKey() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
         RadioMetadata metadata = mBuilder.build();
 
@@ -413,7 +412,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void writeToParcel_forRadioMetadata() {
         RadioMetadata metadataExpected = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
@@ -430,8 +428,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         RadioMetadata metadataExpected = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
                 .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
@@ -443,7 +441,7 @@
         parcel.setDataPosition(0);
 
         RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Radio metadata created from parcel")
+        assertWithMessage("Radio metadata created from parcel with string array type metadata")
                 .that(metadataFromParcel).isEqualTo(metadataExpected);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 7ca806b..4841711 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -36,10 +36,7 @@
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.RemoteException;
-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 org.junit.After;
 import org.junit.Before;
@@ -84,7 +81,7 @@
     private RadioTuner.Callback mCallbackMock;
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -613,9 +610,9 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported()
             throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM))
                 .thenReturn(true);
@@ -626,9 +623,9 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported()
             throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
                 .thenReturn(false);
         when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
@@ -640,9 +637,9 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
             throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
 
@@ -683,8 +680,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
 
         mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
@@ -695,8 +692,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
         when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
                 .thenReturn(false);
@@ -709,9 +706,9 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
             throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
 
         mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 49a7ba8..15bb66b 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -40,10 +40,7 @@
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.UniqueProgramIdentifier;
 import android.os.ServiceSpecificException;
-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 com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
@@ -159,7 +156,7 @@
     @Rule
     public final Expect expect = Expect.create();
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
@@ -317,9 +314,10 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void identifierToHalProgramIdentifier_withFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         ProgramSelector.Identifier hdLocationId = createHdStationLocationIdWithFlagEnabled();
+
         ProgramIdentifier halHdLocationId =
                 ConversionUtils.identifierToHalProgramIdentifier(hdLocationId);
 
@@ -336,10 +334,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void identifierFromHalProgramIdentifier_withFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         ProgramSelector.Identifier hdLocationIdExpected =
                 createHdStationLocationIdWithFlagEnabled();
+
         ProgramSelector.Identifier hdLocationId =
                 ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID);
 
@@ -348,8 +347,9 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void identifierFromHalProgramIdentifier_withFlagDisabled_returnsNull() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         ProgramSelector.Identifier hdLocationId =
                 ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID);
 
@@ -432,8 +432,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void programSelectorMeetsSdkVersionRequirement_withLowerVersionSecondaryId() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         ProgramSelector hdSelector = createHdSelectorWithFlagEnabled();
 
         expect.withMessage("Selector %s with secondary id requiring higher-version SDK version",
@@ -449,8 +449,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionAndFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         ProgramSelector hdSelector = createHdSelectorWithFlagEnabled();
 
         expect.withMessage("Selector %s with required SDK version and feature flag enabled",
@@ -548,8 +548,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM;
 
         expect.withMessage("Force Analog FM flag with required SDK version and feature flag"
@@ -558,8 +558,8 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM;
 
         expect.withMessage("Force Analog FM with required SDK version and with feature flag"
@@ -586,8 +586,9 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void radioMetadataFromHalMetadata_withFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata(
                 new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART});
 
@@ -605,8 +606,9 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void radioMetadataFromHalMetadata_withFlagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
+
         RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata(
                 new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART});
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 7bef5ab..296c451 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -53,10 +53,7 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 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.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -164,7 +161,7 @@
     @Rule
     public final Expect expect = Expect.create();
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
@@ -1231,8 +1228,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void onConfigFlagUpdated_withRequiredFlagEnabled_invokesCallbacks() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         openAidlClients(/* numClients= */ 1);
 
         mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true);
@@ -1242,9 +1239,9 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void onConfigFlagUpdated_withRequiredFlagDisabled_doesNotInvokeCallbacks()
             throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         openAidlClients(/* numClients= */ 1);
 
         mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true);
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4b02257..2327b20 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -42,6 +42,7 @@
 import android.app.PictureInPictureParams;
 import android.app.ResourcesManager;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
+import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
@@ -73,6 +74,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.content.ReferrerIntent;
+import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -227,7 +229,8 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread);
-            transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
+            addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+                    newConfig, DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -243,7 +246,7 @@
             newConfig.seq++;
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread);
-            transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+            addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                     activity.getActivityToken(), newConfig));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -444,16 +447,16 @@
         activity.mTestLatch = new CountDownLatch(1);
 
         ClientTransaction transaction = newTransaction(activityThread);
-        transaction.addCallback(ConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
                 processConfigLandscape, DEVICE_ID_INVALID));
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread);
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                 activity.getActivityToken(), activityConfigLandscape));
-        transaction.addCallback(ConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
                 processConfigPortrait, DEVICE_ID_INVALID));
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+        addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
                 activity.getActivityToken(), activityConfigPortrait));
         appThread.scheduleTransaction(transaction);
 
@@ -840,8 +843,8 @@
                         false /* shouldSendCompatFakeFocus*/);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(callbackItem);
-        transaction.setLifecycleStateRequest(resumeStateRequest);
+        addClientTransactionItem(transaction, callbackItem);
+        addClientTransactionItem(transaction, resumeStateRequest);
 
         return transaction;
     }
@@ -853,7 +856,7 @@
                         false /* shouldSendCompatFakeFocus */);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.setLifecycleStateRequest(resumeStateRequest);
+        addClientTransactionItem(transaction, resumeStateRequest);
 
         return transaction;
     }
@@ -864,7 +867,7 @@
                 activity.getActivityToken(), 0 /* configChanges */);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.setLifecycleStateRequest(stopStateRequest);
+        addClientTransactionItem(transaction, stopStateRequest);
 
         return transaction;
     }
@@ -876,7 +879,7 @@
                 activity.getActivityToken(), config);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(item);
+        addClientTransactionItem(transaction, item);
 
         return transaction;
     }
@@ -888,7 +891,7 @@
                 resume);
 
         final ClientTransaction transaction = newTransaction(activity);
-        transaction.addCallback(item);
+        addClientTransactionItem(transaction, item);
 
         return transaction;
     }
@@ -903,6 +906,17 @@
         return ClientTransaction.obtain(activityThread.getApplicationThread());
     }
 
+    private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
+            @NonNull ClientTransactionItem item) {
+        if (Flags.bundleClientTransactionFlag()) {
+            transaction.addTransactionItem(item);
+        } else if (item.isActivityLifecycleItem()) {
+            transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
+        } else {
+            transaction.addCallback(item);
+        }
+    }
+
     // Test activity
     public static class TestActivity extends Activity {
         static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 443dcb4..2315a58 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -59,6 +60,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -80,20 +83,32 @@
 @Presubmit
 public class TransactionExecutorTests {
 
+    @Mock
+    private ClientTransactionHandler mTransactionHandler;
+    @Mock
+    private ActivityLifecycleItem mActivityLifecycleItem;
+    @Mock
+    private IBinder mActivityToken;
+    @Mock
+    private Activity mActivity;
+
     private TransactionExecutor mExecutor;
     private TransactionExecutorHelper mExecutorHelper;
-    private ClientTransactionHandler mTransactionHandler;
     private ActivityClientRecord mClientRecord;
 
     @Before
     public void setUp() throws Exception {
-        mTransactionHandler = mock(ClientTransactionHandler.class);
+        MockitoAnnotations.initMocks(this);
 
         mClientRecord = new ActivityClientRecord();
         when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord);
 
         mExecutor = spy(new TransactionExecutor(mTransactionHandler));
         mExecutorHelper = new TransactionExecutorHelper();
+
+        doReturn(true).when(mActivityLifecycleItem).isActivityLifecycleItem();
+        doReturn(mActivityToken).when(mActivityLifecycleItem).getActivityToken();
+        doReturn(mActivity).when(mTransactionHandler).getActivity(mActivityToken);
     }
 
     @Test
@@ -229,23 +244,21 @@
         when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
         ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
         when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
-        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
-        IBinder token = mock(IBinder.class);
-        when(stateRequest.getActivityToken()).thenReturn(token);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
-        transaction.setLifecycleStateRequest(stateRequest);
+        transaction.setLifecycleStateRequest(mActivityLifecycleItem);
 
         transaction.preExecute(mTransactionHandler);
         mExecutor.execute(transaction);
 
-        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2,
+                mActivityLifecycleItem);
         inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
         inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
+                any());
     }
 
     @Test
@@ -254,23 +267,21 @@
         when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
         ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
         when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
-        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
-        IBinder token = mock(IBinder.class);
-        when(stateRequest.getActivityToken()).thenReturn(token);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(callback1);
         transaction.addTransactionItem(callback2);
-        transaction.addTransactionItem(stateRequest);
+        transaction.addTransactionItem(mActivityLifecycleItem);
 
         transaction.preExecute(mTransactionHandler);
         mExecutor.execute(transaction);
 
-        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2,
+                mActivityLifecycleItem);
         inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
         inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
+                any());
     }
 
     @Test
@@ -536,42 +547,36 @@
 
     @Test
     public void testActivityItemExecute() {
-        final IBinder token = mock(IBinder.class);
         final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
         when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
-        when(activityItem.getActivityToken()).thenReturn(token);
+        when(activityItem.getActivityToken()).thenReturn(mActivityToken);
         transaction.addCallback(activityItem);
-        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
-        transaction.setLifecycleStateRequest(stateRequest);
-        when(stateRequest.getActivityToken()).thenReturn(token);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+        transaction.setLifecycleStateRequest(mActivityLifecycleItem);
 
         mExecutor.execute(transaction);
 
-        final InOrder inOrder = inOrder(activityItem, stateRequest);
+        final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem);
         inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
+                any());
     }
 
     @Test
     public void testExecuteTransactionItems_activityItemExecute() {
-        final IBinder token = mock(IBinder.class);
         final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
         when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
-        when(activityItem.getActivityToken()).thenReturn(token);
+        when(activityItem.getActivityToken()).thenReturn(mActivityToken);
         transaction.addTransactionItem(activityItem);
-        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
-        transaction.addTransactionItem(stateRequest);
-        when(stateRequest.getActivityToken()).thenReturn(token);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+        transaction.addTransactionItem(mActivityLifecycleItem);
 
         mExecutor.execute(transaction);
 
-        final InOrder inOrder = inOrder(activityItem, stateRequest);
+        final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem);
         inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
+                any());
     }
 
     private static int[] shuffledArray(int[] inputArray) {
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 2ec58d4..9dce899 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -27,6 +28,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.content.res.Configuration;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.test.suitebuilder.annotation.LargeTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -43,11 +45,29 @@
 @LargeTest
 public class ParcelableUsageEventListTest {
     private static final int SMALL_TEST_EVENT_COUNT = 100;
-    private static final int LARGE_TEST_EVENT_COUNT = 10000;
+    private static final int LARGE_TEST_EVENT_COUNT = 30000;
 
     private Random mRandom = new Random();
 
     @Test
+    public void testNullList() throws Exception {
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeParcelable(new ParcelableUsageEventList(null), 0);
+            fail("Expected IllegalArgumentException with null list.");
+        } catch (IllegalArgumentException expected) {
+            // Expected.
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testEmptyList() throws Exception {
+        testParcelableUsageEventList(0);
+    }
+
+    @Test
     public void testSmallList() throws Exception {
         testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
     }
@@ -58,15 +78,15 @@
     }
 
     private void testParcelableUsageEventList(int eventCount) throws Exception {
-        List<Event> smallList = new ArrayList<>();
+        List<Event> eventList = new ArrayList<>();
         for (int i = 0; i < eventCount; i++) {
-            smallList.add(generateUsageEvent());
+            eventList.add(generateUsageEvent());
         }
 
         ParcelableUsageEventList slice;
         Parcel parcel = Parcel.obtain();
         try {
-            parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+            parcel.writeParcelable(new ParcelableUsageEventList(eventList), 0);
             parcel.setDataPosition(0);
             slice = parcel.readParcelable(getClass().getClassLoader(),
                     ParcelableUsageEventList.class);
@@ -79,7 +99,7 @@
         assertEquals(eventCount, slice.getList().size());
 
         for (int i = 0; i < eventCount; i++) {
-            compareUsageEvent(smallList.get(i), slice.getList().get(i));
+            compareUsageEvent(eventList.get(i), slice.getList().get(i));
         }
     }
 
@@ -121,6 +141,12 @@
             case Event.LOCUS_ID_SET:
                 event.mLocusId = anyString();
                 break;
+            case Event.USER_INTERACTION:
+                PersistableBundle extras = new PersistableBundle();
+                extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, anyString());
+                extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, anyString());
+                event.mExtras = extras;
+                break;
         }
 
         event.mFlags = anyInt();
@@ -157,6 +183,14 @@
             case Event.LOCUS_ID_SET:
                 assertEquals(ue1.mLocusId, ue2.mLocusId);
                 break;
+            case Event.USER_INTERACTION:
+                final PersistableBundle extras1 = ue1.getExtras();
+                final PersistableBundle extras2 = ue2.getExtras();
+                assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                        extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY));
+                assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                        extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION));
+                break;
         }
 
         assertEquals(ue1.mFlags, ue2.mFlags);
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
index 083e37a..fae7148 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -72,7 +72,7 @@
             "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId",
             "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage",
             "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId",
-            "mLocusIdToken"};
+            "mLocusIdToken", "mExtras", "mUserInteractionExtrasToken"};
     // All fields in this list are defined in UsageEvents.Event but not persisted
     private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations",
             "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP",
diff --git a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
new file mode 100644
index 0000000..e19c4b1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link android.content.pm.LauncherActivityInfo}
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LauncherActivityInfoTest {
+
+    @Test
+    public void testTrimStart() {
+        // Invisible case
+        assertThat(LauncherActivityInfo.trimStart("\u0009").toString()).isEmpty();
+        // It is not supported in the system font
+        assertThat(LauncherActivityInfo.trimStart("\u0FE1").toString()).isEmpty();
+        // Surrogates case
+        assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36").toString())
+                .isEqualTo("\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trimStart("\u0009\u0FE1\uD83E\uDD36A").toString())
+                .isEqualTo("\uD83E\uDD36A");
+        assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
+        assertThat(LauncherActivityInfo.trimStart("A\uD83E\uDD36\u0009\u0FE1A").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
+        assertThat(LauncherActivityInfo.trimStart(
+                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trimStart(
+                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
+    }
+
+    @Test
+    public void testTrimEnd() {
+        // Invisible case
+        assertThat(LauncherActivityInfo.trimEnd("\u0009").toString()).isEmpty();
+        // It is not supported in the system font
+        assertThat(LauncherActivityInfo.trimEnd("\u0FE1").toString()).isEmpty();
+        // Surrogates case
+        assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36").toString())
+                .isEqualTo("\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trimEnd("\u0009\u0FE1\uD83E\uDD36A").toString())
+                .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
+        assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\uD83E\uDD36A");
+        assertThat(LauncherActivityInfo.trimEnd("A\uD83E\uDD36\u0009\u0FE1A").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
+        assertThat(LauncherActivityInfo.trimEnd(
+                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trimEnd(
+                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
+    }
+
+    @Test
+    public void testTrim() {
+        // Invisible case
+        assertThat(LauncherActivityInfo.trim("\u0009").toString()).isEmpty();
+        // It is not supported in the system font
+        assertThat(LauncherActivityInfo.trim("\u0FE1").toString()).isEmpty();
+        // Surrogates case
+        assertThat(LauncherActivityInfo.trim("\uD83E\uDD36").toString())
+                .isEqualTo("\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trim("\u0009\u0FE1\uD83E\uDD36A").toString())
+                .isEqualTo("\uD83E\uDD36A");
+        assertThat(LauncherActivityInfo.trim("\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\uD83E\uDD36A");
+        assertThat(LauncherActivityInfo.trim("A\uD83E\uDD36\u0009\u0FE1A").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
+        assertThat(LauncherActivityInfo.trim(
+                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
+                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
+        assertThat(LauncherActivityInfo.trim(
+                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
+                .isEqualTo("\uD83E\uDD36A");
+    }
+}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 9b4dec4..20ba427 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -182,42 +182,4 @@
         s.setPreferPowerEfficiency(true);
         s.setPreferPowerEfficiency(true);
     }
-
-    @Test
-    public void testReportActualWorkDurationWithWorkDurationClass() {
-        Session s = createSession();
-        assumeNotNull(s);
-        s.updateTargetWorkDuration(16);
-        s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
-        s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
-        s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
-    }
-
-    @Test
-    public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() {
-        Session s = createSession();
-        assumeNotNull(s);
-        s.updateTargetWorkDuration(16);
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
-        });
-        assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
-        });
-    }
 }
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
index 42629ba..612562e 100644
--- a/core/tests/coretests/src/android/service/notification/ConditionTest.java
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -16,17 +16,22 @@
 
 package android.service.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import android.app.Flags;
 import android.net.Uri;
 import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.google.common.base.Strings;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,8 +42,11 @@
 public class ConditionTest {
     private static final String CLASS = "android.service.notification.Condition";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
-    public void testLongFields_inConstructors() {
+    public void testLongFields_inConstructors_classic() {
         String longString = Strings.repeat("A", 65536);
         Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
 
@@ -59,7 +67,7 @@
     }
 
     @Test
-    public void testLongFields_viaParcel() {
+    public void testLongFields_viaParcel_classic() {
         // Set fields via reflection to force them to be long, then parcel and unparcel to make sure
         // it gets truncated upon unparcelling.
         Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
@@ -98,4 +106,92 @@
         assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length());
         assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length());
     }
+
+    @Test
+    public void testLongFields_inConstructors() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        String longString = Strings.repeat("A", 65536);
+        Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+
+        // Confirm strings are truncated via short constructor
+        Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE,
+                Condition.SOURCE_CONTEXT);
+
+        assertThat(cond1.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(cond1.summary).hasLength(Condition.MAX_STRING_LENGTH);
+
+        // Confirm strings are truncated via long constructor
+        Condition cond2 = new Condition(longUri, longString, longString, longString,
+                -1, Condition.STATE_TRUE, Condition.SOURCE_CONTEXT, Condition.FLAG_RELEVANT_ALWAYS);
+
+        assertThat(cond2.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(cond2.summary).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(cond2.line1).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(cond2.line2).hasLength(Condition.MAX_STRING_LENGTH);
+    }
+
+    @Test
+    public void testLongFields_viaParcel() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        // Set fields via reflection to force them to be long, then parcel and unparcel to make sure
+        // it gets truncated upon unparcelling.
+        Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                Condition.STATE_TRUE, Condition.SOURCE_CONTEXT);
+
+        String longString = Strings.repeat("A", 65536);
+        Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+        Field id = Class.forName(CLASS).getDeclaredField("id");
+        id.setAccessible(true);
+        id.set(cond, longUri);
+        Field summary = Class.forName(CLASS).getDeclaredField("summary");
+        summary.setAccessible(true);
+        summary.set(cond, longString);
+        Field line1 = Class.forName(CLASS).getDeclaredField("line1");
+        line1.setAccessible(true);
+        line1.set(cond, longString);
+        Field line2 = Class.forName(CLASS).getDeclaredField("line2");
+        line2.setAccessible(true);
+        line2.set(cond, longString);
+
+        Parcel parcel = Parcel.obtain();
+        cond.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Condition fromParcel = new Condition(parcel);
+        assertThat(fromParcel.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(fromParcel.summary).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(fromParcel.line1).hasLength(Condition.MAX_STRING_LENGTH);
+        assertThat(fromParcel.line2).hasLength(Condition.MAX_STRING_LENGTH);
+    }
+
+    @Test
+    public void testEquals() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        Condition cond1 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
+        Condition cond2 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                "", "", -1,
+                Condition.STATE_TRUE, Condition.SOURCE_SCHEDULE, Condition.FLAG_RELEVANT_ALWAYS);
+
+        assertThat(cond1).isNotEqualTo(cond2);
+        Condition cond3 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                Condition.STATE_TRUE, Condition.SOURCE_SCHEDULE);
+        assertThat(cond3).isEqualTo(cond2);
+    }
+
+    @Test
+    public void testParcelConstructor() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+                Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
+
+        Parcel parcel = Parcel.obtain();
+        cond.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Condition fromParcel = new Condition(parcel);
+        assertThat(fromParcel).isEqualTo(cond);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 9595332..e1bcd4a 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InsetsSource.ID_IME_CAPTION_BAR;
 import static android.view.WindowInsets.Type.FIRST;
 import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.SIZE;
@@ -52,12 +53,15 @@
 
     private final InsetsSource mSource = new InsetsSource(0 /* id */, navigationBars());
     private final InsetsSource mImeSource = new InsetsSource(1 /* id */, ime());
+    private final InsetsSource mImeCaptionSource = new InsetsSource(
+            ID_IME_CAPTION_BAR, captionBar());
     private final InsetsSource mCaptionSource = new InsetsSource(2 /* id */, captionBar());
 
     @Before
     public void setUp() {
         mSource.setVisible(true);
         mImeSource.setVisible(true);
+        mImeCaptionSource.setVisible(true);
         mCaptionSource.setVisible(true);
     }
 
@@ -110,6 +114,18 @@
     }
 
     @Test
+    public void testCalculateInsets_imeCaptionBar() {
+        mImeCaptionSource.setFrame(new Rect(0, 400, 500, 500));
+        Insets insets = mImeCaptionSource.calculateInsets(new Rect(0, 0, 500, 500), false);
+        assertEquals(Insets.of(0, 0, 0, 100), insets);
+
+        // Place caption bar at top; IME caption bar must always return bottom insets
+        mImeCaptionSource.setFrame(new Rect(0, 0, 500, 100));
+        insets = mImeCaptionSource.calculateInsets(new Rect(0, 0, 500, 500), false);
+        assertEquals(Insets.of(0, 0, 0, 100), insets);
+    }
+
+    @Test
     public void testCalculateInsets_caption_resizing() {
         mCaptionSource.setFrame(new Rect(0, 0, 100, 100));
         Insets insets = mCaptionSource.calculateInsets(new Rect(0, 0, 200, 200), false);
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index d47d789..1cdcb37 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -17,11 +17,15 @@
 package android.view.contentcapture;
 
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED;
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -29,14 +33,20 @@
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
 import android.os.Handler;
-import android.os.Looper;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.autofill.AutofillId;
 import android.view.contentprotection.ContentProtectionEventProcessor;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -56,8 +66,9 @@
  * <p>Run with: {@code atest
  * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
 @SmallTest
+@TestableLooper.RunWithLooper
 public class MainContentCaptureSessionTest {
 
     private static final int BUFFER_SIZE = 100;
@@ -75,6 +86,8 @@
     private static final ContentCaptureManager.StrippedContext sStrippedContext =
             new ContentCaptureManager.StrippedContext(sContext);
 
+    private TestableLooper mTestableLooper;
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock private IContentCaptureManager mMockSystemServerInterface;
@@ -83,12 +96,18 @@
 
     @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
 
+    @Before
+    public void setup() {
+        mTestableLooper = TestableLooper.get(this);
+    }
+
     @Test
     public void onSessionStarted_contentProtectionEnabled_processorCreated() {
         MainContentCaptureSession session = createSession();
         assertThat(session.mContentProtectionEventProcessor).isNull();
 
         session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+        mTestableLooper.processAllMessages();
 
         assertThat(session.mContentProtectionEventProcessor).isNotNull();
     }
@@ -102,6 +121,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+        mTestableLooper.processAllMessages();
 
         assertThat(session.mContentProtectionEventProcessor).isNull();
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -122,6 +142,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+        mTestableLooper.processAllMessages();
 
         assertThat(session.mContentProtectionEventProcessor).isNull();
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -142,6 +163,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+        mTestableLooper.processAllMessages();
 
         assertThat(session.mContentProtectionEventProcessor).isNull();
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -153,6 +175,7 @@
         session.mComponentName = null;
 
         session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+        mTestableLooper.processAllMessages();
 
         assertThat(session.mContentProtectionEventProcessor).isNull();
     }
@@ -166,6 +189,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.sendEvent(EVENT);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         assertThat(session.mEvents).isNull();
@@ -180,6 +204,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.sendEvent(EVENT);
+        mTestableLooper.processAllMessages();
 
         verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
         assertThat(session.mEvents).isNull();
@@ -194,6 +219,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.sendEvent(EVENT);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         assertThat(session.mEvents).isNotNull();
@@ -206,6 +232,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.sendEvent(EVENT);
+        mTestableLooper.processAllMessages();
 
         verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
         assertThat(session.mEvents).isNotNull();
@@ -220,6 +247,7 @@
                         /* enableContentProtectionReceiver= */ true);
 
         session.sendEvent(EVENT);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         assertThat(session.mEvents).isNull();
@@ -236,6 +264,7 @@
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
         session.flush(REASON);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         verifyZeroInteractions(mMockContentCaptureDirectManager);
@@ -252,6 +281,7 @@
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
         session.flush(REASON);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         verifyZeroInteractions(mMockContentCaptureDirectManager);
@@ -269,6 +299,7 @@
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
         session.flush(REASON);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         assertThat(session.mEvents).isEmpty();
@@ -286,6 +317,7 @@
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
         session.flush(REASON);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
         assertThat(session.mEvents).isEmpty();
@@ -298,6 +330,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.destroySession();
+        mTestableLooper.processAllMessages();
 
         verify(mMockSystemServerInterface).finishSession(anyInt());
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -311,6 +344,7 @@
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
         session.resetSession(/* newState= */ 0);
+        mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockSystemServerInterface);
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -318,6 +352,111 @@
         assertThat(session.mContentProtectionEventProcessor).isNull();
     }
 
+    @Test
+    @SuppressWarnings("GuardedBy")
+    public void notifyContentCaptureEvents_notStarted_ContentCaptureDisabled_ProtectionDisabled() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+
+        notifyContentCaptureEvents(session);
+        mTestableLooper.processAllMessages();
+
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    @SuppressWarnings("GuardedBy")
+    public void notifyContentCaptureEvents_started_ContentCaptureDisabled_ProtectionDisabled() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+
+        session.onSessionStarted(0x2, null);
+        notifyContentCaptureEvents(session);
+        mTestableLooper.processAllMessages();
+
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    @SuppressWarnings("GuardedBy")
+    public void notifyContentCaptureEvents_notStarted_ContentCaptureEnabled_ProtectionEnabled() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSession session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        notifyContentCaptureEvents(session);
+        mTestableLooper.processAllMessages();
+
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    @SuppressWarnings("GuardedBy")
+    public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled()
+            throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSession session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        // Override the processor for interaction verification.
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+        notifyContentCaptureEvents(session);
+        mTestableLooper.processAllMessages();
+
+        // Force flush will happen twice.
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any());
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any());
+        // Other than the five view events, there will be two additional tree appearing events.
+        verify(mMockContentProtectionEventProcessor, times(7)).processEvent(any());
+        assertThat(session.mEvents).isEmpty();
+    }
+
+    /** Simulates the regular content capture events sequence. */
+    private void notifyContentCaptureEvents(final MainContentCaptureSession session) {
+        final ArrayList<Object> events = new ArrayList<>(
+                List.of(
+                        prepareView(session),
+                        prepareView(session),
+                        new AutofillId(0),
+                        prepareView(session),
+                        Insets.of(0, 0, 0, 0)
+                )
+        );
+
+        final SparseArray<ArrayList<Object>> contentCaptureEvents = new SparseArray<>();
+        contentCaptureEvents.set(session.getId(), events);
+
+        session.notifyContentCaptureEvents(contentCaptureEvents);
+    }
+
+    private View prepareView(final MainContentCaptureSession session) {
+        final View view = new View(sContext);
+        view.setContentCaptureSession(session);
+        return view;
+    }
+
     private static ContentCaptureOptions createOptions(
             boolean enableContentCaptureReceiver,
             ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
@@ -354,7 +493,7 @@
                 new MainContentCaptureSession(
                         sStrippedContext,
                         manager,
-                        new Handler(Looper.getMainLooper()),
+                        Handler.createAsync(mTestableLooper.getLooper()),
                         mMockSystemServerInterface);
         session.mComponentName = COMPONENT_NAME;
         return session;
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 6229530..3147eac 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -21,7 +21,7 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
-import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF;
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE;
 import static android.window.SystemPerformanceHinter.HINT_ADPF;
 import static android.window.SystemPerformanceHinter.HINT_ALL;
 import static android.window.SystemPerformanceHinter.HINT_SF_EARLY_WAKEUP;
@@ -170,7 +170,7 @@
         // Verify we call SF
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_CATEGORY_DEFAULT),
@@ -262,7 +262,7 @@
         // Verify we call SF and perf manager to clean up
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_CATEGORY_DEFAULT),
@@ -283,7 +283,7 @@
             // Verify we call SF and perf manager to clean up
             verify(mTransaction).setFrameRateSelectionStrategy(
                     eq(mDefaultDisplayRoot),
-                    eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                    eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
             verify(mTransaction).setFrameRateCategory(
                     eq(mDefaultDisplayRoot),
                     eq(FRAME_RATE_CATEGORY_DEFAULT),
@@ -334,7 +334,7 @@
         // Verify we call SF and perf manager to clean up
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_CATEGORY_DEFAULT),
@@ -385,7 +385,7 @@
         session1.close();
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_CATEGORY_DEFAULT),
@@ -410,7 +410,7 @@
                 anyInt());
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+                eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
                 eq(FRAME_RATE_CATEGORY_DEFAULT),
diff --git a/core/tests/coretests/src/com/android/internal/jank/DisplayRefreshRateTest.java b/core/tests/coretests/src/com/android/internal/jank/DisplayRefreshRateTest.java
new file mode 100644
index 0000000..725885a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/DisplayRefreshRateTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.jank;
+
+import static com.android.internal.jank.DisplayRefreshRate.REFRESH_RATE_120_HZ;
+import static com.android.internal.jank.DisplayRefreshRate.REFRESH_RATE_240_HZ;
+import static com.android.internal.jank.DisplayRefreshRate.REFRESH_RATE_30_HZ;
+import static com.android.internal.jank.DisplayRefreshRate.REFRESH_RATE_60_HZ;
+import static com.android.internal.jank.DisplayRefreshRate.REFRESH_RATE_90_HZ;
+import static com.android.internal.jank.DisplayRefreshRate.getRefreshRate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class DisplayRefreshRateTest {
+    @Test
+    public void testRefreshRateMapping() {
+        assertThat(getRefreshRate((long) 1e9 / 30)).isEqualTo(REFRESH_RATE_30_HZ);
+        assertThat(getRefreshRate((long) 1e9 / 60)).isEqualTo(REFRESH_RATE_60_HZ);
+        assertThat(getRefreshRate((long) 1e9 / 90)).isEqualTo(REFRESH_RATE_90_HZ);
+        assertThat(getRefreshRate((long) 1e9 / 120)).isEqualTo(REFRESH_RATE_120_HZ);
+        assertThat(getRefreshRate((long) 1e9 / 240)).isEqualTo(REFRESH_RATE_240_HZ);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index dbbd705..1b9717a 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -70,6 +70,8 @@
 @SmallTest
 public class FrameTrackerTest {
     private static final String CUJ_POSTFIX = "";
+    private static final long FRAME_TIME_60Hz = (long) 1e9 / 60;
+
     private ViewAttachTestActivity mActivity;
 
     @Rule
@@ -170,6 +172,7 @@
         verify(tracker, never()).triggerPerfetto();
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
@@ -207,6 +210,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
@@ -244,6 +248,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
@@ -281,6 +286,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
@@ -321,6 +327,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
@@ -363,6 +370,7 @@
         verify(tracker, never()).triggerPerfetto();
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
@@ -491,6 +499,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
@@ -528,6 +537,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
@@ -565,6 +575,7 @@
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
@@ -596,6 +607,7 @@
         verify(tracker).triggerPerfetto();
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
+                eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
                 eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
                 eq(6L) /* totalFrames */,
                 eq(5L) /* missedFrames */,
@@ -648,7 +660,7 @@
         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(tracker).postCallback(captor.capture());
         mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
-                new JankData(vsyncId, jankType)
+                new JankData(vsyncId, jankType, FRAME_TIME_60Hz)
         });
         captor.getValue().run();
     }
diff --git a/core/tests/timetests/TEST_MAPPING b/core/tests/timetests/TEST_MAPPING
index 5748044..0796d5a 100644
--- a/core/tests/timetests/TEST_MAPPING
+++ b/core/tests/timetests/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeCoreTests"
     }
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 580e73c..06340a2 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -35,6 +35,7 @@
         "androidx.test.ext.junit",
         "truth",
         "servicestests-utils",
+        "ravenwood-junit",
     ],
 
     libs: [
@@ -50,3 +51,22 @@
     test_suites: ["device-tests"],
 
 }
+
+android_ravenwood_test {
+    name: "FrameworksUtilTestsRavenwood",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+    ],
+    srcs: [
+        "src/android/util/DataUnitTest.java",
+        "src/android/util/EventLogTest.java",
+        "src/android/util/IndentingPrintWriterTest.java",
+        "src/android/util/IntArrayTest.java",
+        "src/android/util/LocalLogTest.java",
+        "src/android/util/LongArrayTest.java",
+        "src/android/util/SlogTest.java",
+        "src/android/util/TimeUtilsTest.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/utiltests/src/android/util/DataUnitTest.java
similarity index 85%
rename from core/tests/coretests/src/android/util/DataUnitTest.java
rename to core/tests/utiltests/src/android/util/DataUnitTest.java
index 034cbdd..af9ebc8 100644
--- a/core/tests/coretests/src/android/util/DataUnitTest.java
+++ b/core/tests/utiltests/src/android/util/DataUnitTest.java
@@ -16,12 +16,18 @@
 
 package android.util;
 
-import androidx.test.filters.SmallTest;
+import static org.junit.Assert.assertEquals;
 
-import junit.framework.TestCase;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
-public class DataUnitTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class DataUnitTest {
+    @Test
     public void testSi() throws Exception {
         assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12));
         assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12));
@@ -29,6 +35,7 @@
         assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12));
     }
 
+    @Test
     public void testIec() throws Exception {
         assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12));
         assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12));
diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/utiltests/src/android/util/EventLogTest.java
similarity index 78%
rename from core/tests/coretests/src/android/util/EventLogTest.java
rename to core/tests/utiltests/src/android/util/EventLogTest.java
index 94e72c4..0ebf2c1 100644
--- a/core/tests/coretests/src/android/util/EventLogTest.java
+++ b/core/tests/utiltests/src/android/util/EventLogTest.java
@@ -16,23 +16,42 @@
 
 package android.util;
 
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.EventLog.Event;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.AssertionFailedError;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Unit tests for {@link android.util.EventLog} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
 public class EventLogTest {
+    @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
+    public void testSimple() throws Throwable {
+        EventLog.writeEvent(42, 42);
+        EventLog.writeEvent(42, 42L);
+        EventLog.writeEvent(42, 42f);
+        EventLog.writeEvent(42, "forty-two");
+        EventLog.writeEvent(42, 42, "forty-two", null, 42);
+    }
+
+    @Test
+    @IgnoreUnderRavenwood(reason = "Reading not yet supported")
     public void testWithNewData() throws Throwable {
         Event event = createEvent(() -> {
             EventLog.writeEvent(314,  123);
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/utiltests/src/android/util/LocalLogTest.java
similarity index 93%
rename from core/tests/coretests/src/android/util/LocalLogTest.java
rename to core/tests/utiltests/src/android/util/LocalLogTest.java
index d4861cd..5025a3d 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/utiltests/src/android/util/LocalLogTest.java
@@ -16,9 +16,13 @@
 
 package android.util;
 
-import androidx.test.filters.LargeTest;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -27,13 +31,16 @@
 import java.util.List;
 
 @LargeTest
-public class LocalLogTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class LocalLogTest {
 
+    @Test
     public void testA_localTimestamps() {
         boolean localTimestamps = true;
         doTestA(localTimestamps);
     }
 
+    @Test
     public void testA_nonLocalTimestamps() {
         boolean localTimestamps = false;
         doTestA(localTimestamps);
@@ -49,6 +56,7 @@
         testcase(new LocalLog(10, localTimestamps), lines, want);
     }
 
+    @Test
     public void testB() {
         String[] lines = {
             "foo",
@@ -59,6 +67,7 @@
         testcase(new LocalLog(0), lines, want);
     }
 
+    @Test
     public void testC() {
         String[] lines = {
             "dropped",
diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java
new file mode 100644
index 0000000..6f761e3
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/SlogTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SlogTest {
+    private static final String TAG = "tag";
+    private static final String MSG = "msg";
+    private static final Throwable THROWABLE = new Throwable();
+
+    @Test
+    public void testSimple() {
+        Slog.v(TAG, MSG);
+        Slog.d(TAG, MSG);
+        Slog.i(TAG, MSG);
+        Slog.w(TAG, MSG);
+        Slog.e(TAG, MSG);
+    }
+
+    @Test
+    public void testThrowable() {
+        Slog.v(TAG, MSG, THROWABLE);
+        Slog.d(TAG, MSG, THROWABLE);
+        Slog.i(TAG, MSG, THROWABLE);
+        Slog.w(TAG, MSG, THROWABLE);
+        Slog.e(TAG, MSG, THROWABLE);
+    }
+}
diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
new file mode 100644
index 0000000..e8246c8
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeUtilsTest {
+    public static final long SECOND_IN_MILLIS = 1000;
+    public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+    public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+    public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+    public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+
+    @Test
+    public void testFormatTime() {
+        assertEquals("1672556400000 (now)",
+                TimeUtils.formatTime(1672556400000L, 1672556400000L));
+        assertEquals("1672556400000 (in 10 ms)",
+                TimeUtils.formatTime(1672556400000L, 1672556400000L - 10));
+        assertEquals("1672556400000 (10 ms ago)",
+                TimeUtils.formatTime(1672556400000L, 1672556400000L + 10));
+
+        // Uses formatter above, so we just care that it doesn't crash
+        TimeUtils.formatRealtime(1672556400000L);
+        TimeUtils.formatUptime(1672556400000L);
+    }
+
+    @Test
+    public void testFormatDuration_Zero() {
+        assertEquals("0", TimeUtils.formatDuration(0));
+    }
+
+    @Test
+    public void testFormatDuration_Negative() {
+        assertEquals("-10ms", TimeUtils.formatDuration(-10));
+    }
+
+    @Test
+    public void testFormatDuration() {
+        long accum = 900;
+        assertEquals("+900ms", TimeUtils.formatDuration(accum));
+
+        accum += 59 * SECOND_IN_MILLIS;
+        assertEquals("+59s900ms", TimeUtils.formatDuration(accum));
+
+        accum += 59 * MINUTE_IN_MILLIS;
+        assertEquals("+59m59s900ms", TimeUtils.formatDuration(accum));
+
+        accum += 23 * HOUR_IN_MILLIS;
+        assertEquals("+23h59m59s900ms", TimeUtils.formatDuration(accum));
+
+        accum += 6 * DAY_IN_MILLIS;
+        assertEquals("+6d23h59m59s900ms", TimeUtils.formatDuration(accum));
+    }
+
+    @Test
+    public void testDumpTime() {
+        assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> {
+            TimeUtils.dumpTime(pw, 1672556400000L);
+        }));
+        assertEquals("2023-01-01 00:00:00.000 (now)", runWithPrintWriter((pw) -> {
+            TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L);
+        }));
+        assertEquals("2023-01-01 00:00:00.000 (-10ms)", runWithPrintWriter((pw) -> {
+            TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L + 10);
+        }));
+    }
+
+    @Test
+    public void testFormatForLogging() {
+        assertEquals("unknown", TimeUtils.formatForLogging(0));
+        assertEquals("unknown", TimeUtils.formatForLogging(-1));
+        assertEquals("unknown", TimeUtils.formatForLogging(Long.MIN_VALUE));
+        assertEquals("2023-01-01 00:00:00", TimeUtils.formatForLogging(1672556400000L));
+    }
+
+    @Test
+    public void testLogTimeOfDay() {
+        assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L));
+    }
+
+    public static String runWithPrintWriter(Consumer<PrintWriter> consumer) {
+        final StringWriter sw = new StringWriter();
+        consumer.accept(new PrintWriter(sw));
+        return sw.toString();
+    }
+
+    public static String runWithStringBuilder(Consumer<StringBuilder> consumer) {
+        final StringBuilder sb = new StringBuilder();
+        consumer.accept(sb);
+        return sb.toString();
+    }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 32186667..1f08955 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -467,6 +467,7 @@
         <permission name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
         <permission name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
         <permission name="android.permission.MANAGE_APP_HIBERNATION"/>
+        <permission name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" />
         <!-- Permission required for CTS test - ResourceObserverNativeTest -->
         <permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
         <!-- Permission required for CTS test - MediaCodecResourceTest -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f19acbe..2237ba1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -529,6 +529,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1582845629": {
+      "message": "Starting animation on %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1575977269": {
       "message": "Skipping %s: mismatch root %s",
       "level": "DEBUG",
@@ -925,6 +931,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1243510456": {
+      "message": "Dim animation requested: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1237827119": {
       "message": "Schedule remove starting %s startingWindow=%s animate=%b Callers=%s",
       "level": "VERBOSE",
@@ -991,12 +1003,6 @@
       "group": "WM_DEBUG_WINDOW_INSETS",
       "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
     },
-    "-1176488860": {
-      "message": "SURFACE isSecure=%b: %s",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
-    },
     "-1164930508": {
       "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
       "level": "VERBOSE",
@@ -1177,6 +1183,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "-1028213464": {
+      "message": "%s skipping animation and directly setting alpha=%f, blur=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1022146708": {
       "message": "Skipping %s: mismatch activity type",
       "level": "DEBUG",
@@ -1801,12 +1813,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-504637678": {
-      "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_DIMMER",
-      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
-    },
     "-503656156": {
       "message": "Update process config of %s to new config %s",
       "level": "VERBOSE",
@@ -3277,6 +3283,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "810599500": {
+      "message": "SURFACE isSecure=%b: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "829434921": {
       "message": "Draw state now committed in %s",
       "level": "VERBOSE",
@@ -4027,12 +4039,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1620751818": {
-      "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_DIMMER",
-      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
-    },
     "1621562070": {
       "message": "    startWCT=%s",
       "level": "VERBOSE",
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
index 07f1d4a..8dc9579 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java
@@ -63,7 +63,7 @@
 
     @Override
     public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
-        final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree);
+        final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree, state);
         final String sourceCode = state.getSourceCode().toString();
         for (ErrorProneToken token : ErrorProneTokens.getTokens(sourceCode, state.context)) {
             for (Tokens.Comment comment : token.comments()) {
@@ -112,9 +112,9 @@
     }
 
 
-    private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree) {
+    private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree, VisitorState state) {
         Map<Integer, Tree> javadoccableTrees = new HashMap<>();
-        new SuppressibleTreePathScanner<Void, Void>() {
+        new SuppressibleTreePathScanner<Void, Void>(state) {
             @Override
             public Void visitClass(ClassTree classTree, Void unused) {
                 javadoccableTrees.put(getStartPosition(classTree), classTree);
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index 9a0a22a..6c81a60 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -5,4 +5,11 @@
      namespace: "core_graphics"
      description: "Add a function without unused exact param for computeBounds."
      bug: "304478551"
+}
+
+flag {
+     name: "yuv_image_compress_to_ultra_hdr"
+     namespace: "core_graphics"
+     description: "Feature flag for YUV image compress to Ultra HDR."
+     bug: "308978825"
 }
\ No newline at end of file
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 2ec4524..d659ddd 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -402,8 +402,8 @@
     }
 
     @Override
-    public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii,
-            @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) {
+    public final void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
+            @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
         nDrawDoubleRoundRect(mNativeCanvasWrapper,
                 outer.left, outer.top, outer.right, outer.bottom, outerRadii,
                 inner.left, inner.top, inner.right, inner.bottom, innerRadii,
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
index 1e03c53..2b170b9a 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -29,6 +29,7 @@
  * Insets are immutable so may be treated as values.
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Insets implements Parcelable {
     public static final @NonNull Insets NONE = new Insets(0, 0, 0, 0);
 
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index 2781ac4b..e5c620b 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -24,6 +24,7 @@
 /**
  * Point holds two integer coordinates
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Point implements Parcelable {
     public int x;
     public int y;
diff --git a/graphics/java/android/graphics/PointF.java b/graphics/java/android/graphics/PointF.java
index ed9df14..3531785 100644
--- a/graphics/java/android/graphics/PointF.java
+++ b/graphics/java/android/graphics/PointF.java
@@ -23,6 +23,7 @@
 /**
  * PointF holds two float coordinates
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PointF implements Parcelable {
     public float x;
     public float y;
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 1a522bd..411a10b 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -45,6 +45,7 @@
  * into the column and row described by its left and top coordinates, but not
  * those of its bottom and right.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Rect implements Parcelable {
     public int left;
     public int top;
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index 1d294d5..ff50a0c 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -32,6 +32,7 @@
  * the rectangle's width and height. Note: most methods do not check to see that
  * the coordinates are sorted correctly (i.e. left <= right and top <= bottom).
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RectF implements Parcelable {
     public float left;
     public float top;
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index 6b5238b..ce35b55 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -16,6 +16,9 @@
 
 package android.graphics;
 
+import com.android.graphics.flags.Flags;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import java.io.OutputStream;
@@ -243,6 +246,36 @@
                 new byte[WORKING_COMPRESS_STORAGE]);
     }
 
+  /**
+   * Compress the HDR image into JPEG/R format.
+   *
+   * Sample usage:
+   *     hdr_image.compressToJpegR(sdr_image, 90, stream);
+   *
+   * For the SDR image, only YUV_420_888 image format is supported, and the following
+   * color spaces are supported:
+   *     ColorSpace.Named.SRGB,
+   *     ColorSpace.Named.DISPLAY_P3
+   *
+   * For the HDR image, only YCBCR_P010 image format is supported, and the following
+   * color spaces are supported:
+   *     ColorSpace.Named.BT2020_HLG,
+   *     ColorSpace.Named.BT2020_PQ
+   *
+   * @param sdr       The SDR image, only ImageFormat.YUV_420_888 is supported.
+   * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
+   *                  small size, 100 meaning compress for max quality.
+   * @param stream    OutputStream to write the compressed data.
+   * @return          True if the compression is successful.
+   * @throws IllegalArgumentException if input images are invalid; quality is not within [0,
+   *                  100]; or stream is null.
+   */
+    public boolean compressToJpegR(@NonNull YuvImage sdr, int quality,
+            @NonNull OutputStream stream) {
+        byte[] emptyExif = new byte[0];
+        return compressToJpegR(sdr, quality, stream, emptyExif);
+    }
+
     /**
      * Compress the HDR image into JPEG/R format.
      *
@@ -263,12 +296,14 @@
      * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
      *                  small size, 100 meaning compress for max quality.
      * @param stream    OutputStream to write the compressed data.
+     * @param exif      Exchangeable image file format.
      * @return          True if the compression is successful.
      * @throws IllegalArgumentException if input images are invalid; quality is not within [0,
      *                  100]; or stream is null.
      */
+    @FlaggedApi(Flags.FLAG_YUV_IMAGE_COMPRESS_TO_ULTRA_HDR)
     public boolean compressToJpegR(@NonNull YuvImage sdr, int quality,
-            @NonNull OutputStream stream) {
+            @NonNull OutputStream stream, @NonNull byte[] exif) {
         if (sdr == null) {
             throw new IllegalArgumentException("SDR input cannot be null");
         }
@@ -304,7 +339,8 @@
       return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(),
                                    sdr.getYuvData(), sdr.getColorSpace().getDataSpace(),
                                    mWidth, mHeight, quality, stream,
-                                   new byte[WORKING_COMPRESS_STORAGE]);
+                                   new byte[WORKING_COMPRESS_STORAGE], exif,
+                                   mStrides, sdr.getStrides());
   }
 
 
@@ -416,5 +452,6 @@
 
     private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId,
             byte[] sdr, int sdrColorSpaceId, int width, int height, int quality,
-            OutputStream stream, byte[] tempStorage);
+            OutputStream stream, byte[] tempStorage, byte[] exif,
+            int[] hdrStrides, int[] sdrStrides);
 }
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 5e16bce..dd703f5 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -33,7 +33,6 @@
 import android.util.Log;
 
 import java.util.Calendar;
-import java.util.Objects;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -139,13 +138,25 @@
         return new KeyStore2();
     }
 
+    /**
+     * Gets the {@link IKeystoreService} that should be started in early_hal in Android.
+     *
+     * @throws IllegalStateException if the KeystoreService is not available or has not
+     * been initialized when called. This is a state that should not happen and indicates
+     * and error somewhere in the stack or with the calling processes access permissions.
+     */
     @NonNull private synchronized IKeystoreService getService(boolean retryLookup) {
         if (mBinder == null || retryLookup) {
             mBinder = IKeystoreService.Stub.asInterface(ServiceManager
-                    .getService(KEYSTORE2_SERVICE_NAME));
-            Binder.allowBlocking(mBinder.asBinder());
+                .getService(KEYSTORE2_SERVICE_NAME));
         }
-        return Objects.requireNonNull(mBinder);
+        if (mBinder == null) {
+            throw new IllegalStateException(
+                    "Could not connect to Keystore service. Keystore may have crashed or not been"
+                            + " initialized");
+        }
+        Binder.allowBlocking(mBinder.asBinder());
+        return mBinder;
     }
 
     void delete(KeyDescriptor descriptor) throws KeyStoreException {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index b714035..231fa48 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -1282,15 +1282,18 @@
          * function (MGF1) with a digest.
          * The default digest for MGF1 is {@code SHA-1}, which will be specified during key creation
          * time if no digests have been explicitly provided.
-         * When using the key, the caller may not specify any digests that were not provided during
-         * key creation time. The caller may specify the default digest, {@code SHA-1}, if no
+         * {@code null} may not be specified as a parameter to this method: It is not possible to
+         * disable MGF1 digest, a default must be present for when the caller tries to use it.
+         *
+         * <p>When using the key, the caller may not specify any digests that were not provided
+         * during key creation time. The caller may specify the default digest, {@code SHA-1}, if no
          * digests were explicitly provided during key creation (but it is not necessary to do so).
          *
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
         @FlaggedApi("MGF1_DIGEST_SETTER")
-        public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
+        public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 4d73c20..ca3d8d1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -375,7 +375,8 @@
             return TaskFragmentAnimationParams.DEFAULT;
         }
         return new TaskFragmentAnimationParams.Builder()
-                .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+                // TODO(b/263047900): Update extensions API.
+                // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
                 .build();
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index faf7c39..b5c32bb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -854,7 +854,8 @@
         return new SplitAttributes.Builder()
                 .setSplitType(splitTypeToUpdate)
                 .setLayoutDirection(splitAttributes.getLayoutDirection())
-                .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+                // TODO(b/263047900): Update extensions API.
+                // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
                 .build();
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 9607b78..60beb0b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -17,6 +17,7 @@
 package androidx.window.extensions;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.ActivityTaskManager;
@@ -69,6 +70,7 @@
                 .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
         assertThat(splitAttributes.getSplitType())
                 .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
-        assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+        // TODO(b/263047900): Update extensions API.
+        // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
     }
 }
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index c366ccd..4d2d960 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -42,3 +42,11 @@
     description: "Enables PiP UI state callback on entering"
     bug: "303718131"
 }
+
+flag {
+    name: "enable_pip2_implementation"
+    namespace: "multitasking"
+    description: "Enables the new implementation of PiP (PiP2)"
+    bug: "290220798"
+    is_fixed_read_only: true
+}
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 2471cba..90b13cd 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"App sal dalk nie met verdeelde skerm werk nie"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App steun nie verdeelde skerm nie"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Hierdie app kan net in 1 venster oopgemaak word"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Skermverdeler"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 387478c..478585a 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገፅ ጋር ላይሠራ ይችላል"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገፅ ከፋይ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index fb92ed7..b2a522c 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"قد لا يعمل التطبيق بشكل سليم في وضع تقسيم الشاشة."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"لا يعمل التطبيق في وضع تقسيم الشاشة."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"أداة تقسيم الشاشة"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 5e44e47..897c38f 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 4e2f9b9..4854e0d 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Tətbiq bölünmüş ekranda işləməyə bilər"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Tətbiq bölünmüş ekranı dəstəkləmir"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu tətbiq yalnız 1 pəncərədə açıla bilər"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcısı"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 990aed8..cac4e67 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podeljeni ekran."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova aplikacija može da se otvori samo u jednom prozoru"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Razdelnik podeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index febb064..cac76df 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Праграма можа не працаваць у рэжыме падзеленага экрана"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Праграма не падтрымлівае рэжым падзеленага экрана"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Гэту праграму можна адкрыць толькі ў адным акне"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Раздзяляльнік падзеленага экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 8bbdf91..ac9a208 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Приложението може да не работи в режим на разделен екран"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложението не поддържа разделен екран"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Това приложение може да се отвори само в 1 прозорец"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Разделител в режима за разделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4678b17..3b83dcb 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"স্প্লিট স্ক্রিনে এই অ্যাপ নাও কাজ করতে পারে"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"স্প্লিট স্ক্রিনে এই অ্যাপ কাজ করে না"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"এই অ্যাপটি শুধুমাত্র ১টি উইন্ডোতে খোলা যেতে পারে"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্রিন বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 076a17d..813d163 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati na podijeljenom ekranu"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni ekran"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova aplikacija se može otvoriti samo u 1 prozoru"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 3115998..d00c50b 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"És possible que l\'aplicació no funcioni amb la pantalla dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'aplicació no admet la pantalla dividida"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aquesta aplicació només pot obrir-se en 1 finestra"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Separador de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index fe8a7ee..40132e4 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikace v režimu rozdělené obrazovky nemusí fungovat"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikace nepodporuje režim rozdělené obrazovky"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Tuto aplikaci lze otevřít jen v jednom okně"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Čára rozdělující obrazovku"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 81c5b22..6e9738d 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Appen fungerer muligvis ikke i opdelt skærm"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen understøtter ikke opdelt skærm"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denne app kan kun åbnes i 1 vindue"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Adskiller til opdelt skærm"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 4b9556d..5da224d 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert im Splitscreen-Modus unter Umständen nicht"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Splitscreen wird in dieser App nicht unterstützt"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Diese App kann nur in einem einzigen Fenster geöffnet werden"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Bildschirmteiler"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 907ef38..822b552 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Αυτή η εφαρμογή μπορεί να ανοίξει μόνο σε ένα παράθυρο"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Διαχωριστικό οθόνης"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 843c8ed..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 0d9d09a..c0c46cd 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in 1 window"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 843c8ed..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 843c8ed..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 4fbb5b8..f089938 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎App may not work with split screen‎‏‎‎‏‎"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎App does not support split screen‎‏‎‎‏‎"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎This app can only be opened in 1 window‎‏‎‎‏‎"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎App does not support launch on secondary displays.‎‏‎‎‏‎"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎Split screen divider‎‏‎‎‏‎"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 9aa985d..6bbc1e3 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Es posible que la app no funcione en el modo de pantalla dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La app no es compatible con la función de pantalla dividida"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta app solo puede estar abierta en 1 ventana"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index e51f735..c662ff6 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Puede que la aplicación no funcione con la pantalla dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La aplicación no es compatible con la pantalla dividida"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta aplicación solo puede abrirse en una ventana"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index b3f30e7..ade5e2d 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Rakendus ei pruugi jagatud ekraanikuvaga töötada."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Rakendus ei toeta jagatud ekraanikuva."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Selle rakenduse saab avada ainult ühes aknas"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Jagatud ekraanikuva jaotur"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 0d9d706..d6cb668 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikazioak ez du onartzen pantaila zatitua"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Leiho bakar batean ireki daiteke aplikazioa"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da exekutatu bigarren mailako pantailatan."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 267daaa..ba0f51c 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفی‌سازی"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن است برنامه با صفحهٔ دونیمه کار نکند"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"برنامه از صفحهٔ دونیمه پشتیبانی نمی‌کند"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"این برنامه فقط در ۱ پنجره می‌تواند باز شود"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راه‌اندازی در نمایشگرهای ثانویه پشتیبانی نمی‌کند."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"تقسیم‌کننده صفحهٔ دونیمه"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 7be75573..a53f861 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Sovellus ei ehkä toimi jaetulla näytöllä"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Sovellus ei tue jaetun näytön tilaa"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Tämän sovelluksen voi avata vain yhdessä ikkunassa"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Näytönjakaja"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 5cee037..4563556 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge l\'écran partagé"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette application ne peut être ouverte que dans une seule fenêtre."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 0b3b364..8957571 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner en mode Écran partagé"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appli incompatible avec l\'écran partagé"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette appli ne peut être ouverte que dans 1 fenêtre"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index a46c9e7..54c864e 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"É posible que a aplicación non funcione coa pantalla dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A aplicación non admite a función de pantalla dividida"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta aplicación só se pode abrir en 1 ventá"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index ad27a79..2b09279 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"વિભાજિત સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ઍપ વિભાજિત સ્ક્રીનને સપોર્ટ કરતી નથી"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 9b10fdc..35b099a 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"मुमकिन है कि ऐप्लिकेशन, स्प्लिट स्क्रीन मोड में काम न करे"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यह ऐप्लिकेशन, स्प्लिट स्क्रीन मोड पर काम नहीं करता"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index ae4ff2d..f2c3c22 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni zaslon"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova se aplikacija može otvoriti samo u jednom prozoru"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog zaslona"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 070743c..d94bb29 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Az alkalmazás nem támogatja az osztott képernyőt"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ez az alkalmazás csak egy ablakban nyitható meg"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Elválasztó az osztott képernyős nézetben"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index dfdc974..f2945c1 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Հավելվածը չի աջակցում էկրանի տրոհումը"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Տրոհված էկրանի բաժանիչ"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index f6161a9..c39b429 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikasi mungkin tidak berfungsi dengan layar terpisah"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikasi tidak mendukung layar terpisah"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aplikasi ini hanya dapat dibuka di 1 jendela"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Pembagi layar terpisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 0cc1006..630eaa3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Forritið virkar hugsanlega ekki með skjáskiptingu"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Forritið styður ekki skjáskiptingu"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aðeins er hægt að opna þetta forrit í 1 glugga"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Skilrúm skjáskiptingar"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index bddde70..77893c9 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'app potrebbe non funzionare con lo schermo diviso"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'app non supporta la modalità schermo diviso"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Questa app può essere aperta soltanto in 1 finestra"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Strumento per schermo diviso"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 32b75ec..4f28c23 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"יכול להיות שהאפליקציה לא תפעל עם מסך מפוצל"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"האפליקציה לא תומכת במסך מפוצל"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"מחלק מסך מפוצל"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index ea19f5a..60b4d7e 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"アプリは分割画面では動作しないことがあります"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"アプリで分割画面がサポートされていません"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"このアプリはウィンドウが 1 つの場合のみ開くことができます"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"分割画面の分割線"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 1a4d17c..28d2257 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ეკრანის გაყოფის გამყოფი"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 4d5ac83..441df8d7 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Қолданбада экранды бөлу мүмкін емес."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлу режимінің бөлгіші"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index f90ca9a..efa6418 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"កម្មវិធី​អាចមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"កម្មវិធីមិនអាចប្រើមុខងារ​បំបែកអេក្រង់បានទេ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"អាចបើកកម្មវិធីនេះបានតែក្នុងវិនដូ 1 ប៉ុណ្ណោះ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធី​នេះមិន​អាច​ចាប់ផ្តើម​នៅលើ​អេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារ​បំបែកអេក្រង់"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 57aa771..0cda445 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್‌ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index e777fa7..676506f 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"앱이 화면 분할 모드로는 작동하지 않을 수 있습니다"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"앱이 화면 분할을 지원하지 않습니다"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"이 앱은 창 1개에서만 열 수 있습니다."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"화면 분할기"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index a96f3c8..57253ef 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Колдонмодо экран бөлүнбөшү мүмкүн"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Колдонмодо экран бөлүнбөйт"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Бул колдонмону 1 терезеде гана ачууга болот"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлгүч"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 6ab04c5..c5f6e22 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບໂໝດແບ່ງໜ້າຈໍ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 9ef541e..eeed5a4 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Programa gali neveikti naudojant išskaidyto ekrano režimą"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programoje nepalaikomas išskaidyto ekrano režimas"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Šią programą galima atidaryti tik viename lange"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Išskaidyto ekrano režimo daliklis"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 634db04..4324d468 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Lietotnē netiek atbalstīta ekrāna sadalīšana"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Šo lietotni var atvērt tikai vienā logā"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Ekrāna sadalītājs"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 6272b05..471f5bd 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликацијата можеби нема да работи со поделен екран"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликацијата не поддржува поделен екран"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Апликацијава може да се отвори само во еден прозорец"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Разделник на поделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index dcc12a7..5bc694a 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"സ്‌ക്രീൻ വിഭജന മോഡിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്‌പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 5ff9c97..0268c64 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Апп дэлгэцийг хуваах горимтой ажиллахгүй байж магадгүй"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апп дэлгэцийг хуваах горимыг дэмждэггүй"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Дэлгэцийг хуваах хуваагч"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 1dc4151..2e6163e 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करणार नाही"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"अ‍ॅप हे स्प्लिट स्क्रीनला सपोर्ट करत नाही"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अ‍ॅप लाँच होणार नाही."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index c20f2b1..a60e61b 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Apl mungkin tidak berfungsi dengan skrin pisah"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Apl tidak menyokong skrin pisah"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Apl ini hanya boleh dibuka dalam 1 tetingkap"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Pembahagi skrin pisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index c07511b..6b91d46 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို မပံ့ပိုးပါ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 458487c..ec9ece3 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Det kan hende at appen ikke fungerer med delt skjerm"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen støtter ikke delt skjerm"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denne appen kan bare åpnes i ett vindu"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Skilleelement for delt skjerm"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 09d8396..8bb07be 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"यो एपले स्प्लिट स्क्रिन मोडमा काम नगर्न सक्छ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यो एप स्प्लिट स्क्रिन मोडमा प्रयोग गर्न मिल्दैन"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रिन डिभाइडर"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 8b91fa2..c6c60ae 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"De app werkt misschien niet met gesplitst scherm"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App ondersteunt geen gesplitst scherm"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Deze app kan maar in 1 venster worden geopend"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Scheiding voor gesplitst scherm"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index de1c998..927dde4 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରେ ଆପ କାମ କରିନପାରେ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 2a700d3..0e12fb8 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 43cfa64..75a8ce6 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacja może nie działać przy podzielonym ekranie"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacja nie obsługuje podzielonego ekranu"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Linia dzielenia ekranu"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index fb1ef68..b84a0de 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esse app só pode ser aberto em uma única janela"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 702c043..d84bfcd 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"A app pode não funcionar com o ecrã dividido"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A app não é compatível com o ecrã dividido"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta app só pode ser aberta em 1 janela"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor do ecrã dividido"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index fb1ef68..b84a0de 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esse app só pode ser aberto em uma única janela"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 596eebc..eeea428 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplicația nu acceptă ecranul împărțit"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aplicația se poate deschide într-o singură fereastră"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Separator pentru ecranul împărțit"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f2f1f6f..26b0f94 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Когда включено разделение экрана, приложение может работать нестабильно."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложение не поддерживает разделение экрана."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Это приложение можно открыть только в одном окне."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Разделитель экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 0a1fd94..9b9a430 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"යෙදුම බෙදීම් තිරය සමග ක්‍රියා නොකළ හැක"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"යෙදුම බෙදුම් තිරයට සහාය නොදක්වයි"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"බෙදුම් තිර වෙන්කරණය"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 6e746b0..4b21531 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikácia nemusí fungovať s rozdelenou obrazovkou"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikácia nepodporuje rozdelenú obrazovku"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Táto aplikácia môže byť otvorená iba v jednom okne"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Rozdeľovač obrazovky"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 9536e05..581cf5b 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"To aplikacijo je mogoče odpreti samo v enem oknu"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Razdelilnik zaslonov"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index a0fff68..9dc7b7e 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacioni mund të mos funksionojë me ekranin e ndarë"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacioni nuk mbështet ekranin e ndarë"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ky aplikacion mund të hapet vetëm në 1 dritare"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Ndarësi i ekranit të ndarë"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index ad9ba90..cd532f7 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликација можда неће радити са подељеним екраном."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликација не подржава подељени екран."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ова апликација може да се отвори само у једном прозору"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Разделник подељеног екрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 488af39..386dda7 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Appen kanske inte fungerar med delad skärm"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen har inte stöd för delad skärm"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denna app kan bara vara öppen i ett fönster"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Avdelare för delad skärm"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 38cbfe3..69b2e34 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Huenda programu isifanye kazi kwenye skrini iliyogawanywa"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programu haifanyi kazi kwenye skrini iliyogawanywa"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Kitenganishi cha kugawa skrini"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 616aa27..037b5ab 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"திரைப் பிரிப்புப் பயன்முறையில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"திரைப் பிரிப்புப் பயன்முறையை ஆப்ஸ் ஆதரிக்காது"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"திரைப் பிரிப்பான்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 6fe1995..694ecb9 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"స్ప్లిట్ స్క్రీన్‌తో యాప్ పని చేయకపోవచ్చు"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"యాప్‌లో స్ప్లిట్ స్క్రీన్‌కు సపోర్ట్ లేదు"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ఈ యాప్‌ను 1 విండోలో మాత్రమే తెరవవచ్చు"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 65e5ff7..d4b6aff 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"แอปอาจใช้ไม่ได้กับโหมดแยกหน้าจอ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"แอปไม่รองรับการแยกหน้าจอ"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"เส้นแยกหน้าจอ"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 74e0abe..db9303c 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Posibleng hindi gumana sa split screen ang app"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Hindi sinusuportahan ng app ang split-screen"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Sa 1 window lang puwedeng buksan ang app na ito"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Divider ng split screen"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 2d16924..818666c 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Uygulama bölünmüş ekranda çalışmayabilir"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Uygulama bölünmüş ekranı desteklemiyor."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu uygulama yalnızca 1 pencerede açılabilir"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcı"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 0103128..85fb8e1 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Додаток може не працювати в режимі розділення екрана"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Додаток не підтримує розділення екрана"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Цей додаток можна відкрити лише в одному вікні"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Розділювач екрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 2fdcfe8..813870b 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ایپ اسپلٹ اسکرین کو سپورٹ نہیں کرتی ہے"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"اسپلٹ اسکرین ڈیوائیڈر"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index a0d7cb6..7bcacbb 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Bu ilovada ekranni ikkiga ajratish rejimi ishlamaydi."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Bu ilovada ekranni ikkiga ajratish ishlamaydi."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu ilovani faqat 1 ta oynada ochish mumkin"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Ekranni ikkiga ajratish chizigʻi"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 957a457..416dd91 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Có thể ứng dụng không dùng được chế độ chia đôi màn hình"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Ứng dụng không hỗ trợ chế độ chia đôi màn hình"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ứng dụng này chỉ có thể mở trong 1 cửa sổ"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Trình chia đôi màn hình"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index e4bf076..6ad1728 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"应用可能无法在分屏模式下正常运行"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"应用不支持分屏"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"此应用只能在 1 个窗口中打开"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"分屏分隔线"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 2007621..b5b94ec 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割螢幕中運作"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"應用程式不支援分割螢幕"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"此應用程式只可在 1 個視窗中開啟"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"分割螢幕分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 1d69edd..0f2a052 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割畫面中運作"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"這個應用程式不支援分割畫面"</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"這個應用程式只能在 1 個視窗中開啟"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"分割畫面分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 099879b..a696f9e 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
     <string name="dock_forced_resizable" msgid="7429086980048964687">"Ama-app okungenzeka angasebenzi ngesikrini esihlukanisiwe"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"I-app ayisekeli isikrini esihlukanisiwe."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5011042177901502928) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Le-app ingavulwa kuphela ewindini eli-1."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"Isihlukanisi sokuhlukanisa isikrini"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 6a6f2b0..e4abae4 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -141,4 +141,7 @@
         window. If false, the splash screen will be a solid color splash screen whenever the
         app has not provided a windowSplashScreenAnimatedIcon. -->
     <bool name="config_canUseAppIconForSplashScreen">true</bool>
+
+    <!-- Whether CompatUIController is enabled -->
+    <bool name="config_enableCompatUIController">true</bool>
 </resources>
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/libs/WindowManager/Shell/res/values/config_tv.xml
similarity index 62%
copy from packages/SystemUI/communal/layout/AndroidManifest.xml
copy to libs/WindowManager/Shell/res/values/config_tv.xml
index 141be07..3da5539 100644
--- a/packages/SystemUI/communal/layout/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/res/values/config_tv.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2023 The Android Open Source Project
+<!--
+    Copyright (C) 2023 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -14,5 +14,9 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-
-<manifest package="com.android.systemui.communal.layout" />
+<resources>
+    <integer name="config_tvPipEnterFadeOutDuration">500</integer>
+    <integer name="config_tvPipEnterFadeInDuration">1500</integer>
+    <integer name="config_tvPipExitFadeOutDuration">500</integer>
+    <integer name="config_tvPipExitFadeInDuration">500</integer>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 6cd1324..efa5a1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -21,6 +21,7 @@
 
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -253,7 +254,8 @@
 
     private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
-        final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
+        final int type = getTransitionTypeFromInfo(info);
+        final Animation a = loadAttributeAnimation(type, info, change, WALLPAPER_TRANSITION_NONE,
                 mTransitionAnimation, false);
         return a != null && a.getShowBackdrop();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 243e2f4..e74e578d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -553,8 +553,9 @@
      * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
      */
     void hideCurrentInputMethod() {
+        int displayId = mWindowManager.getDefaultDisplay().getDisplayId();
         try {
-            mBarService.hideCurrentInputMethodForBubbles();
+            mBarService.hideCurrentInputMethodForBubbles(displayId);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index c7ab6aa..f5b877a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -963,7 +963,11 @@
                 && mTaskView.isAttachedToWindow()) {
             // post this to the looper, because if the device orientation just changed, we need to
             // let the current shell transition complete before updating the task view bounds.
-            post(() -> mTaskView.onLocationChanged());
+            post(() -> {
+                if (mTaskView != null) {
+                    mTaskView.onLocationChanged();
+                }
+            });
         }
         if (mIsOverflow) {
             // post this to the looper so that the view has a chance to be laid out before it can
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e9344ff..1c74f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -322,13 +322,12 @@
         }
 
         @Override
-        public void remove(android.view.IWindow window) throws RemoteException {
-            super.remove(window);
+        public void remove(IBinder clientToken) throws RemoteException {
+            super.remove(clientToken);
             synchronized(this) {
-                IBinder token = window.asBinder();
-                new SurfaceControl.Transaction().remove(mLeashForWindow.get(token))
+                new SurfaceControl.Transaction().remove(mLeashForWindow.get(clientToken))
                     .apply();
-                mLeashForWindow.remove(token);
+                mLeashForWindow.remove(clientToken);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index d520ff7..8b6c7b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -258,7 +258,7 @@
             ActivityTaskManager.getService().onPictureInPictureStateChanged(
                     new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
             );
-        } catch (RemoteException e) {
+        } catch (RemoteException | IllegalStateException e) {
             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Unable to set alert PiP state change.", TAG);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 108aa82..1e30d8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -21,13 +21,13 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.RemoteException
-import android.os.SystemProperties
 import android.util.DisplayMetrics
 import android.util.Log
 import android.util.Pair
 import android.util.TypedValue
 import android.window.TaskSnapshot
 import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.Flags
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import kotlin.math.abs
 
@@ -37,7 +37,6 @@
 
     // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
     private const val EPSILON = 1e-7
-    private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation"
 
     /**
      * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
@@ -138,5 +137,5 @@
 
     @JvmStatic
     val isPip2ExperimentEnabled: Boolean
-        get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false)
+        get() = Flags.enablePip2Implementation()
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 54cf84c..b158f88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -221,34 +221,57 @@
             Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
-            CompatUIController compatUI,
+            Optional<CompatUIController> compatUI,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<RecentTasksController> recentTasksOptional,
-            @ShellMainThread ShellExecutor mainExecutor
-    ) {
+            @ShellMainThread ShellExecutor mainExecutor) {
         if (!context.getResources().getBoolean(R.bool.config_registerShellTaskOrganizerOnInit)) {
             // TODO(b/238217847): Force override shell init if registration is disabled
             shellInit = new ShellInit(mainExecutor);
         }
-        return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
-                unfoldAnimationController, recentTasksOptional, mainExecutor);
+        return new ShellTaskOrganizer(
+                shellInit,
+                shellCommandHandler,
+                compatUI.orElse(null),
+                unfoldAnimationController,
+                recentTasksOptional,
+                mainExecutor);
     }
 
     @WMSingleton
     @Provides
-    static CompatUIController provideCompatUIController(Context context,
+    static Optional<CompatUIController> provideCompatUIController(
+            Context context,
             ShellInit shellInit,
             ShellController shellController,
-            DisplayController displayController, DisplayInsetsController displayInsetsController,
-            DisplayImeController imeController, SyncTransactionQueue syncQueue,
-            @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
-            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
-            CompatUIShellCommandHandler compatUIShellCommandHandler,
-            AccessibilityManager accessibilityManager) {
-        return new CompatUIController(context, shellInit, shellController, displayController,
-                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
-                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler,
-                accessibilityManager);
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            DisplayImeController imeController,
+            SyncTransactionQueue syncQueue,
+            @ShellMainThread ShellExecutor mainExecutor,
+            Lazy<Transitions> transitionsLazy,
+            Lazy<DockStateReader> dockStateReader,
+            Lazy<CompatUIConfiguration> compatUIConfiguration,
+            Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
+            Lazy<AccessibilityManager> accessibilityManager) {
+        if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                new CompatUIController(
+                        context,
+                        shellInit,
+                        shellController,
+                        displayController,
+                        displayInsetsController,
+                        imeController,
+                        syncQueue,
+                        mainExecutor,
+                        transitionsLazy,
+                        dockStateReader.get(),
+                        compatUIConfiguration.get(),
+                        compatUIShellCommandHandler.get(),
+                        accessibilityManager.get()));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a9675f9..1947097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -20,6 +20,8 @@
 import android.os.Handler;
 import android.os.SystemClock;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
@@ -41,7 +43,6 @@
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipBoundsController;
@@ -78,11 +79,12 @@
             PipDisplayLayoutState pipDisplayLayoutState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
+            PipTransitionState pipTransitionState,
             PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             TvPipMenuController tvPipMenuController,
             PipMediaController pipMediaController,
-            PipTransitionController pipTransitionController,
+            TvPipTransition tvPipTransition,
             TvPipNotificationController tvPipNotificationController,
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -99,9 +101,10 @@
                         pipDisplayLayoutState,
                         tvPipBoundsAlgorithm,
                         tvPipBoundsController,
+                        pipTransitionState,
                         pipAppOpsListener,
                         pipTaskOrganizer,
-                        pipTransitionController,
+                        tvPipTransition,
                         tvPipMenuController,
                         pipMediaController,
                         tvPipNotificationController,
@@ -151,25 +154,23 @@
         return new LegacySizeSpecSource(context, pipDisplayLayoutState);
     }
 
-    // Handler needed for loadDrawableAsync() in PipControlsViewController
     @WMSingleton
     @Provides
-    static PipTransitionController provideTvPipTransition(
+    static TvPipTransition provideTvPipTransition(
             Context context,
-            ShellInit shellInit,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Transitions transitions,
+            @NonNull ShellInit shellInit,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @NonNull Transitions transitions,
             TvPipBoundsState tvPipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState,
-            TvPipMenuController pipMenuController,
+            TvPipMenuController tvPipMenuController,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipTransitionState pipTransitionState,
             PipAnimationController pipAnimationController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            PipDisplayLayoutState pipDisplayLayoutState) {
         return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
-                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
-                Optional.empty());
+                tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState,
+                pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState);
     }
 
     @WMSingleton
@@ -207,7 +208,7 @@
             PipTransitionState pipTransitionState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
-            PipTransitionController pipTransitionController,
+            TvPipTransition tvPipTransition,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreenController> splitScreenControllerOptional,
@@ -217,7 +218,7 @@
         return new TvPipTaskOrganizer(context,
                 syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
                 tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
-                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
                 splitScreenControllerOptional, displayController, pipUiEventLogger,
                 shellTaskOrganizer, mainExecutor);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4a9ea6f..144555d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -21,7 +21,6 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
@@ -321,24 +320,10 @@
     }
 
     /** Move a task with given `taskId` to fullscreen */
-    fun moveToFullscreen(taskId: Int) {
-        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
-    }
-
-    /** Move a task to fullscreen */
-    fun moveToFullscreen(task: RunningTaskInfo) {
-        KtProtoLog.v(
-            WM_SHELL_DESKTOP_MODE,
-            "DesktopTasksController: moveToFullscreen taskId=%d",
-            task.taskId
-        )
-
-        val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task)
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
-        } else {
-            shellTaskOrganizer.applyTransaction(wct)
+    fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
+            windowDecor.incrementRelayoutBlock()
+            moveToFullscreenWithAnimation(task, task.positionInParent)
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 75d27d9..95d7ad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -25,12 +25,14 @@
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.protolog.ShellProtoLogGroup
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.util.KtProtoLog
 import com.android.wm.shell.util.TransitionUtil
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
@@ -68,6 +70,10 @@
     private var splitScreenController: SplitScreenController? = null
     private var transitionState: TransitionState? = null
 
+    /** Whether a drag-to-desktop transition is in progress. */
+    val inProgress: Boolean
+        get() = transitionState != null
+
     /** Sets a listener to receive callback about events during the transition animation. */
     fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
         dragToDesktopStateListener = listener
@@ -92,19 +98,22 @@
             dragToDesktopAnimator: MoveToDesktopAnimator,
             windowDecoration: DesktopModeWindowDecoration
     ) {
-        if (transitionState != null) {
+        if (inProgress) {
             error("A drag to desktop is already in progress")
         }
 
         val options = ActivityOptions.makeBasic().apply {
             setTransientLaunch()
             setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis())
+            pendingIntentCreatorBackgroundActivityStartMode =
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
         }
         val pendingIntent = PendingIntent.getActivity(
                 context,
                 0 /* requestCode */,
                 launchHomeIntent,
-                FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT
+                FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
+                options.toBundle()
         )
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle())
@@ -135,6 +144,12 @@
      * inside the desktop drop zone.
      */
     fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+        if (requireTransitionState().startAborted) {
+            // Don't attempt to complete the drag-to-desktop since the start transition didn't
+            // succeed as expected. Just reset the state as if nothing happened.
+            clearState()
+            return
+        }
         transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
     }
 
@@ -147,6 +162,12 @@
      */
     fun cancelDragToDesktopTransition() {
         val state = requireTransitionState()
+        if (state.startAborted) {
+            // Don't attempt to cancel the drag-to-desktop since the start transition didn't
+            // succeed as expected. Just reset the state as if nothing happened.
+            clearState()
+            return
+        }
         state.cancelled = true
         if (state.draggedTaskChange != null) {
             // Regular case, transient launch of Home happened as is waiting for the cancel
@@ -409,6 +430,21 @@
         return null
     }
 
+    override fun onTransitionConsumed(
+        transition: IBinder,
+        aborted: Boolean,
+        finishTransaction: SurfaceControl.Transaction?
+    ) {
+        val state = transitionState ?: return
+        if (aborted && state.startTransitionToken == transition) {
+            KtProtoLog.v(
+                ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                "DragToDesktop: onTransitionConsumed() start transition aborted"
+            )
+            state.startAborted = true
+        }
+    }
+
     private fun isHomeChange(change: Change): Boolean {
         return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME
     }
@@ -508,6 +544,7 @@
         abstract var homeToken: WindowContainerToken?
         abstract var draggedTaskChange: Change?
         abstract var cancelled: Boolean
+        abstract var startAborted: Boolean
 
         data class FromFullscreen(
                 override val draggedTaskId: Int,
@@ -520,6 +557,7 @@
                 override var homeToken: WindowContainerToken? = null,
                 override var draggedTaskChange: Change? = null,
                 override var cancelled: Boolean = false,
+                override var startAborted: Boolean = false,
         ) : TransitionState()
         data class FromSplit(
                 override val draggedTaskId: Int,
@@ -532,6 +570,7 @@
                 override var homeToken: WindowContainerToken? = null,
                 override var draggedTaskChange: Change? = null,
                 override var cancelled: Boolean = false,
+                override var startAborted: Boolean = false,
                 var splitRootChange: Change? = null,
         ) : TransitionState()
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index cbed4b5..a58d94e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -81,15 +81,35 @@
      */
     public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds) {
+        mTmpDestinationRectF.set(destinationBounds);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, RectF destinationBounds) {
         return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
     }
 
     /**
+     * Operates the scale (setMatrix) on a given transaction and leash
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds, float degrees) {
+        mTmpDestinationRectF.set(destinationBounds);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+    }
+
+    /**
      * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
     public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, Rect destinationBounds, float degrees) {
+            Rect sourceBounds, RectF destinationBounds, float degrees) {
         mTmpSourceRectF.set(sourceBounds);
         // We want the matrix to position the surface relative to the screen coordinates so offset
         // the source to 0,0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c05601b..b4067d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -297,9 +297,9 @@
     // changed RunningTaskInfo when it finishes.
     private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
     private WindowContainerToken mToken;
-    private SurfaceControl mLeash;
+    protected SurfaceControl mLeash;
     protected PipTransitionState mPipTransitionState;
-    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     protected PictureInPictureParams mPictureInPictureParams;
     private IntConsumer mOnDisplayIdChangeCallback;
@@ -973,7 +973,7 @@
             return;
         }
 
-        cancelCurrentAnimator();
+        cancelAnimationOnTaskVanished();
         onExitPipFinished(info);
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -981,6 +981,10 @@
         }
     }
 
+    protected void cancelAnimationOnTaskVanished() {
+        cancelCurrentAnimator();
+    }
+
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
@@ -1100,7 +1104,7 @@
     }
 
     /** Called when exiting PIP transition is finished to do the state cleanup. */
-    void onExitPipFinished(TaskInfo info) {
+    public void onExitPipFinished(TaskInfo info) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "onExitPipFinished: %s, state=%s leash=%s",
                 info.topActivity, mPipTransitionState, mLeash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index a48e969f..72c0cd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
+import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -101,12 +102,29 @@
                 && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
                 && !mTvPipBoundsState.isTvPipManuallyCollapsed();
         if (isPipExpanded) {
-            updateGravityOnExpansionToggled(/* expanding= */ true);
+            updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
         }
         mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
         return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
     }
 
+    @Override
+    public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG);
+
+        updateExpandedPipSize();
+        final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported()
+                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
+                && !mTvPipBoundsState.isTvPipManuallyCollapsed();
+        if (isPipExpanded) {
+            updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
+        }
+        mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
+        return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(),
+                Collections.emptySet()).getUnstashedBounds());
+    }
+
     /** Returns the current bounds adjusted to the new aspect ratio, if valid. */
     @Override
     public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
@@ -133,16 +151,25 @@
      */
     @NonNull
     public Placement getTvPipPlacement() {
+        final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+        final Set<Rect> unrestrictedKeepClearAreas =
+                mTvPipBoundsState.getUnrestrictedKeepClearAreas();
+
+        return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+    }
+
+    /**
+     * Calculates the PiP bounds.
+     */
+    @NonNull
+    private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas,
+            Set<Rect> unrestrictedKeepClearAreas) {
         final Size pipSize = getPipSize();
         final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
         final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
         final Rect insetBounds = new Rect();
         getInsetBounds(insetBounds);
 
-        final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
-        final Set<Rect> unrestrictedKeepClearAreas =
-                mTvPipBoundsState.getUnrestrictedKeepClearAreas();
-
         mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
         mKeepClearAlgorithm.setScreenSize(screenSize);
         mKeepClearAlgorithm.setMovementBounds(insetBounds);
@@ -189,8 +216,11 @@
 
         int updatedGravity;
         if (expanding) {
-            // Save collapsed gravity.
-            mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
+            if (!mTvPipBoundsState.isTvPipExpanded()) {
+                // Save collapsed gravity.
+                mTvPipBoundsState.setTvPipPreviousCollapsedGravity(
+                        mTvPipBoundsState.getTvPipGravity());
+            }
 
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
                 updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 2b3a93e..5ee3734e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -131,6 +131,7 @@
         mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
         mTvPipGravity = mDefaultGravity;
         mPreviousCollapsedGravity = mDefaultGravity;
+        mIsTvPipExpanded = false;
         mTvPipManuallyCollapsed = false;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 72115fd..cd3d38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -56,6 +56,7 @@
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
@@ -122,6 +123,7 @@
     private final PipDisplayLayoutState mPipDisplayLayoutState;
     private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
     private final TvPipBoundsController mTvPipBoundsController;
+    private final PipTransitionState mPipTransitionState;
     private final PipAppOpsListener mAppOpsListener;
     private final PipTaskOrganizer mPipTaskOrganizer;
     private final PipMediaController mPipMediaController;
@@ -157,6 +159,7 @@
             PipDisplayLayoutState pipDisplayLayoutState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
+            PipTransitionState pipTransitionState,
             PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
@@ -177,6 +180,7 @@
                 pipDisplayLayoutState,
                 tvPipBoundsAlgorithm,
                 tvPipBoundsController,
+                pipTransitionState,
                 pipAppOpsListener,
                 pipTaskOrganizer,
                 pipTransitionController,
@@ -199,6 +203,7 @@
             PipDisplayLayoutState pipDisplayLayoutState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
+            PipTransitionState pipTransitionState,
             PipAppOpsListener pipAppOpsListener,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
@@ -212,6 +217,7 @@
             Handler mainHandler,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mPipTransitionState = pipTransitionState;
         mMainHandler = mainHandler;
         mMainExecutor = mainExecutor;
         mShellController = shellController;
@@ -365,7 +371,6 @@
                 "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
 
         mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
-        onPipDisappeared();
     }
 
     private void togglePipExpansion() {
@@ -420,6 +425,11 @@
 
     @Override
     public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
+        if (!mPipTransitionState.hasEnteredPip()) {
+            // Do not schedule a move animation while we're still transitioning into/out of PiP
+            return;
+        }
+
         mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
                 animationDuration, null);
         mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
@@ -447,7 +457,7 @@
             return;
         }
         mPipTaskOrganizer.removePip();
-        onPipDisappeared();
+        mTvPipMenuController.closeMenu();
     }
 
     @Override
@@ -477,7 +487,7 @@
         mPipNotificationController.dismiss();
         mActionBroadcastReceiver.unregister();
 
-        mTvPipMenuController.closeMenu();
+        mTvPipMenuController.detach();
         mTvPipActionsProvider.reset();
         mTvPipBoundsState.resetTvPipState();
         mTvPipBoundsController.reset();
@@ -501,8 +511,6 @@
     public void onPipTransitionCanceled(int direction) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
-        mTvPipMenuController.onPipTransitionFinished(
-                PipAnimationController.isInPipDirection(direction));
         mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ee55211..c6803f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -262,8 +262,8 @@
 
     @Override
     public void detach() {
-        closeMenu();
         detachPipMenu();
+        switchToMenuMode(MODE_NO_MENU);
         mLeash = null;
     }
 
@@ -320,10 +320,21 @@
     @Override
     public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
             Rect pipBounds, float alpha) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
+        movePipMenu(pipTx, pipBounds, alpha);
+    }
 
-        if (pipBounds.isEmpty()) {
+    /**
+     * Move the PiP menu with the given bounds and update its opacity.
+     * The PiP SurfaceControl is given if there is a need to synchronize the movements
+     * on the same frame as PiP.
+     */
+    public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds,
+            float alpha) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: movePipMenu: %s, alpha %s", TAG,
+                pipBounds != null ? pipBounds.toShortString() : null, alpha);
+
+        if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) {
             if (pipTx == null) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                         "%s: no transaction given", TAG);
@@ -334,28 +345,36 @@
             return;
         }
 
-        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
-        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
-        final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
         if (pipTx == null) {
             pipTx = new SurfaceControl.Transaction();
         }
-        pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
-        pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+
+        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+
+        if (pipBounds != null) {
+            final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
+            pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
+            pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+            updateMenuBounds(pipBounds);
+        }
 
         if (alpha != ALPHA_NO_CHANGE) {
             pipTx.setAlpha(frontSurface, alpha);
             pipTx.setAlpha(backSurface, alpha);
         }
 
-        // Synchronize drawing the content in the front and back surfaces together with the pip
-        // transaction and the position change for the front and back surfaces
-        final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
-        syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
-        syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
-        updateMenuBounds(pipBounds);
-        syncGroup.addTransaction(pipTx);
-        syncGroup.markSyncReady();
+        if (pipBounds != null) {
+            // Synchronize drawing the content in the front and back surfaces together with the pip
+            // transaction and the position change for the front and back surfaces
+            final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
+            syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
+            syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
+            syncGroup.addTransaction(pipTx);
+            syncGroup.markSyncReady();
+        } else {
+            pipTx.apply();
+        }
     }
 
     private boolean isMenuAttached() {
@@ -388,14 +407,19 @@
         final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
-        mSystemWindows.updateViewLayout(mPipBackgroundView,
-                getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
-                        menuBounds.height()));
-        mSystemWindows.updateViewLayout(mPipMenuView,
-                getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
-                        menuBounds.height()));
-        if (mPipMenuView != null) {
-            mPipMenuView.setPipBounds(pipBounds);
+
+        boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width()
+                || mPipBackgroundView.getLayoutParams().height != menuBounds.height();
+        if (needsRelayout) {
+            mSystemWindows.updateViewLayout(mPipBackgroundView,
+                    getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+                            menuBounds.height()));
+            mSystemWindows.updateViewLayout(mPipMenuView,
+                    getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
+                            menuBounds.height()));
+            if (mPipMenuView != null) {
+                mPipMenuView.setPipBounds(pipBounds);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
index f86f987..202d36f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -168,6 +168,9 @@
      * that the edu text will be marqueed
      */
     private boolean isEduTextMarqueed() {
+        if (mEduTextView.getLayout() == null) {
+            return false;
+        }
         final int availableWidth = (int) mEduTextView.getWidth()
                 - mEduTextView.getCompoundPaddingLeft()
                 - mEduTextView.getCompoundPaddingRight();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index f315afb..21223c9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -35,7 +35,6 @@
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
@@ -46,6 +45,7 @@
  * TV specific changes to the PipTaskOrganizer.
  */
 public class TvPipTaskOrganizer extends PipTaskOrganizer {
+    private final TvPipTransition mTvPipTransition;
 
     public TvPipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
@@ -56,7 +56,7 @@
             @NonNull PipMenuController pipMenuController,
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
-            @NonNull PipTransitionController pipTransitionController,
+            @NonNull TvPipTransition tvPipTransition,
             @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
@@ -65,9 +65,10 @@
             ShellExecutor mainExecutor) {
         super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
                 pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
-                surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
                 splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
                 mainExecutor);
+        mTvPipTransition = tvPipTransition;
     }
 
     @Override
@@ -105,4 +106,14 @@
         // when the menu alpha is 0 (e.g. when a fade-in animation starts).
         return true;
     }
+
+    @Override
+    protected void cancelAnimationOnTaskVanished() {
+        mTvPipTransition.cancelAnimations();
+        if (mLeash != null) {
+            mSurfaceControlTransactionFactory.getTransaction()
+                    .setAlpha(mLeash, 0f)
+                    .apply();
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index f24b2b3..571c839 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -16,43 +16,822 @@
 
 package com.android.wm.shell.pip.tv;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.transitTypeToString;
+
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import android.animation.AnimationHandler;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
 import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.Trace;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
 
+import androidx.annotation.FloatRange;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 /**
  * PiP Transition for TV.
  */
-public class TvPipTransition extends PipTransition {
+public class TvPipTransition extends PipTransitionController {
+    private static final String TAG = "TvPipTransition";
+    private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f;
+
+    private final PipTransitionState mPipTransitionState;
+    private final PipAnimationController mPipAnimationController;
+    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+    private final TvPipMenuController mTvPipMenuController;
+    private final PipDisplayLayoutState mPipDisplayLayoutState;
+    private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory
+            mTransactionFactory;
+
+    private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+            ThreadLocal.withInitial(() -> {
+                AnimationHandler handler = new AnimationHandler();
+                handler.setProvider(new SfVsyncFrameCallbackProvider());
+                return handler;
+            });
+
+    private final long mEnterFadeOutDuration;
+    private final long mEnterFadeInDuration;
+    private final long mExitFadeOutDuration;
+    private final long mExitFadeInDuration;
+
+    @Nullable
+    private Animator mCurrentAnimator;
+
+    /**
+     * The Task window that is currently in PIP windowing mode.
+     */
+    @Nullable
+    private WindowContainerToken mCurrentPipTaskToken;
+
+    @Nullable
+    private IBinder mPendingExitTransition;
 
     public TvPipTransition(Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             TvPipBoundsState tvPipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState,
             TvPipMenuController tvPipMenuController,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipTransitionState pipTransitionState,
             PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<SplitScreenController> splitScreenOptional) {
-        super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
-                pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
-                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
-                splitScreenOptional);
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController,
+                tvPipBoundsAlgorithm);
+        mPipTransitionState = pipTransitionState;
+        mPipAnimationController = pipAnimationController;
+        mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+        mTvPipMenuController = tvPipMenuController;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+        mTransactionFactory =
+                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+
+        mEnterFadeOutDuration = context.getResources().getInteger(
+                R.integer.config_tvPipEnterFadeOutDuration);
+        mEnterFadeInDuration = context.getResources().getInteger(
+                R.integer.config_tvPipEnterFadeInDuration);
+        mExitFadeOutDuration = context.getResources().getInteger(
+                R.integer.config_tvPipExitFadeOutDuration);
+        mExitFadeInDuration = context.getResources().getInteger(
+                R.integer.config_tvPipExitFadeInDuration);
     }
 
+    @Override
+    public void startExitTransition(int type, WindowContainerTransaction out,
+            @Nullable Rect destinationBounds) {
+        cancelAnimations();
+        mPendingExitTransition = mTransitions.startTransition(type, out, this);
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+        if (isCloseTransition(info)) {
+            // PiP is closing (without reentering fullscreen activity)
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Starting close animation", TAG);
+            cancelAnimations();
+            startCloseAnimation(info, startTransaction, finishTransaction, finishCallback);
+            mCurrentPipTaskToken = null;
+            return true;
+
+        } else if (transition.equals(mPendingExitTransition)) {
+            // PiP is exiting (reentering fullscreen activity)
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Starting exit animation", TAG);
+
+            final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+            mPendingExitTransition = null;
+            // PipTaskChange can be null if the PIP task has been detached, for example, when the
+            // task contains multiple activities, the PIP will be moved to a new PIP task when
+            // entering, and be moved back when exiting. In that case, the PIP task will be removed
+            // immediately.
+            final TaskInfo pipTaskInfo = currentPipTaskChange != null
+                    ? currentPipTaskChange.getTaskInfo()
+                    : mPipOrganizer.getTaskInfo();
+            if (pipTaskInfo == null) {
+                throw new RuntimeException("Cannot find the pip task for exit-pip transition.");
+            }
+
+            final int type = info.getType();
+            switch (type) {
+                case TRANSIT_EXIT_PIP -> {
+                    TransitionInfo.Change pipChange = currentPipTaskChange;
+                    SurfaceControl activitySc = null;
+                    if (mCurrentPipTaskToken == null) {
+                        ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
+                    } else if (pipChange == null) {
+                        // The pipTaskChange is null, this can happen if we are reparenting the
+                        // PIP activity back to its original Task. In that case, we should animate
+                        // the activity leash instead, which should be the change whose last parent
+                        // is the recorded PiP Task.
+                        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                            final TransitionInfo.Change change = info.getChanges().get(i);
+                            if (mCurrentPipTaskToken.equals(change.getLastParent())) {
+                                // Find the activity that is exiting PiP.
+                                pipChange = change;
+                                activitySc = change.getLeash();
+                                break;
+                            }
+                        }
+                    }
+                    if (pipChange == null) {
+                        ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                "%s: No window of exiting PIP is found. Can't play expand "
+                                        + "animation",
+                                TAG);
+                        removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction,
+                                finishCallback);
+                        return true;
+                    }
+                    final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
+                    final SurfaceControl pipLeash;
+                    if (activitySc != null) {
+                        // Use a local leash to animate activity in case the activity has
+                        // letterbox which may be broken by PiP animation, e.g. always end at 0,0
+                        // in parent and unable to include letterbox area in crop bounds.
+                        final SurfaceControl activitySurface = pipChange.getLeash();
+                        pipLeash = new SurfaceControl.Builder()
+                                .setName(activitySc + "_pip-leash")
+                                .setContainerLayer()
+                                .setHidden(false)
+                                .setParent(root.getLeash())
+                                .build();
+                        startTransaction.reparent(activitySurface, pipLeash);
+                        // Put the activity at local position with offset in case it is letterboxed.
+                        final Point activityOffset = pipChange.getEndRelOffset();
+                        startTransaction.setPosition(activitySc, activityOffset.x,
+                                activityOffset.y);
+                    } else {
+                        pipLeash = pipChange.getLeash();
+                        startTransaction.reparent(pipLeash, root.getLeash());
+                    }
+                    startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
+                    final Rect currentBounds = mPipBoundsState.getBounds();
+                    final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
+                    cancelAnimations();
+                    startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds,
+                            startTransaction,
+                            finishTransaction, finishCallback);
+                }
+                // pass through here is intended
+                case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo,
+                        startTransaction, finishTransaction,
+                        finishCallback
+                );
+                default -> {
+                    return false;
+                }
+            }
+            mCurrentPipTaskToken = null;
+            return true;
+
+        } else if (isEnteringPip(info)) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Starting enter animation", TAG);
+
+            // Search for an Enter PiP transition
+            TransitionInfo.Change enterPip = null;
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (change.getTaskInfo() != null
+                        && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+                    enterPip = change;
+                }
+            }
+            if (enterPip == null) {
+                throw new IllegalStateException("Trying to start PiP animation without a pip"
+                        + "participant");
+            }
+
+            // Make sure other open changes are visible as entering PIP. Some may be hidden in
+            // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (change == enterPip) continue;
+                if (TransitionUtil.isOpeningType(change.getMode())) {
+                    final SurfaceControl leash = change.getLeash();
+                    startTransaction.show(leash).setAlpha(leash, 1.f);
+                }
+            }
+
+            cancelAnimations();
+            startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task.
+     */
+    private void removePipImmediately(@NonNull TransitionInfo info,
+            @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG);
+        cancelAnimations();
+        startTransaction.apply();
+        finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+                mPipDisplayLayoutState.getDisplayBounds());
+        mTvPipMenuController.detach();
+        mPipOrganizer.onExitPipFinished(taskInfo);
+        finishCallback.onTransitionFinished(/* wct= */ null);
+
+        mPipTransitionState.setTransitionState(UNDEFINED);
+        sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+    }
+
+    private void startCloseAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info);
+        final SurfaceControl pipLeash = pipTaskChange.getLeash();
+
+        final List<SurfaceControl> closeLeashes = new ArrayList<>();
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) {
+                closeLeashes.add(change.getLeash());
+            }
+        }
+
+        final Rect pipBounds = mPipBoundsState.getBounds();
+        mSurfaceTransactionHelper
+                .resetScale(startTransaction, pipLeash, pipBounds)
+                .crop(startTransaction, pipLeash, pipBounds)
+                .shadow(startTransaction, pipLeash, false);
+
+        final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
+        for (SurfaceControl leash : closeLeashes) {
+            startTransaction.setShadowRadius(leash, 0f);
+        }
+
+        ValueAnimator closeFadeOutAnimator = createAnimator();
+        closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+        closeFadeOutAnimator.setDuration(mExitFadeOutDuration);
+        closeFadeOutAnimator.addUpdateListener(
+                animationUpdateListener(pipLeash).fadingOut().withMenu());
+        for (SurfaceControl leash : closeLeashes) {
+            closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut());
+        }
+
+        closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: close animation: start", TAG);
+                for (SurfaceControl leash : closeLeashes) {
+                    startTransaction.setShadowRadius(leash, 0f);
+                }
+                startTransaction.apply();
+
+                mPipTransitionState.setTransitionState(EXITING_PIP);
+                sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK);
+            }
+
+            @Override
+            public void onAnimationCancel(@NonNull Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: close animation: cancel", TAG);
+                sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: close animation: end", TAG);
+                mTvPipMenuController.detach();
+                finishCallback.onTransitionFinished(null /* wct */);
+                transaction.close();
+                mPipTransitionState.setTransitionState(UNDEFINED);
+                sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+
+                mCurrentAnimator = null;
+            }
+        });
+
+        closeFadeOutAnimator.start();
+        mCurrentAnimator = closeFadeOutAnimator;
+    }
+
+    @Override
+    public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // Keep track of the PIP task
+        mCurrentPipTaskToken = pipChange.getContainer();
+        final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
+        final SurfaceControl leash = pipChange.getLeash();
+
+        mTvPipMenuController.attach(leash);
+        setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
+                taskInfo.topActivityInfo);
+
+        final Rect pipBounds =
+                mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas();
+        mPipBoundsState.setBounds(pipBounds);
+        mTvPipMenuController.movePipMenu(null, pipBounds, 0f);
+
+        final WindowContainerTransaction resizePipWct = new WindowContainerTransaction();
+        resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+        resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+        resizePipWct.setBounds(taskInfo.token, pipBounds);
+
+        mSurfaceTransactionHelper
+                .resetScale(finishTransaction, leash, pipBounds)
+                .crop(finishTransaction, leash, pipBounds)
+                .shadow(finishTransaction, leash, false);
+
+        final Rect currentBounds = pipChange.getStartAbsBounds();
+        final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+        final ValueAnimator enterFadeOutAnimator = createAnimator();
+        enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+        enterFadeOutAnimator.setDuration(mEnterFadeOutDuration);
+        enterFadeOutAnimator.addUpdateListener(
+                animationUpdateListener(leash)
+                        .fadingOut()
+                        .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds));
+
+        enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+            @SuppressLint("MissingPermission")
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: enter fade out animation: end", TAG);
+                SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
+                mSurfaceTransactionHelper
+                        .resetScale(tx, leash, pipBounds)
+                        .crop(tx, leash, pipBounds)
+                        .shadow(tx, leash, false);
+                mShellTaskOrganizer.applyTransaction(resizePipWct);
+                tx.apply();
+            }
+        });
+
+        final ValueAnimator enterFadeInAnimator = createAnimator();
+        enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+        enterFadeInAnimator.setDuration(mEnterFadeInDuration);
+        enterFadeInAnimator.addUpdateListener(
+                animationUpdateListener(leash)
+                        .fadingIn()
+                        .withMenu()
+                        .atBounds(pipBounds));
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet
+                .play(enterFadeInAnimator)
+                .after(500)
+                .after(enterFadeOutAnimator);
+
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: enter animation: start", TAG);
+                startTransaction.apply();
+                mPipTransitionState.setTransitionState(ENTERING_PIP);
+                sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: enter animation: cancel", TAG);
+                enterFadeInAnimator.setCurrentFraction(1f);
+                sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: enter animation: end", TAG);
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+                wct.setBounds(taskInfo.token, pipBounds);
+                finishCallback.onTransitionFinished(wct);
+
+                mPipTransitionState.setTransitionState(ENTERED_PIP);
+                sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+                mCurrentAnimator = null;
+            }
+        });
+
+        animatorSet.start();
+        mCurrentAnimator = animatorSet;
+    }
+
+    private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash,
+            Rect currentBounds, Rect destinationBounds,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+        final ValueAnimator exitFadeOutAnimator = createAnimator();
+        exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+        exitFadeOutAnimator.setDuration(mExitFadeOutDuration);
+        exitFadeOutAnimator.addUpdateListener(
+                animationUpdateListener(leash)
+                        .fadingOut()
+                        .withMenu()
+                        .atBounds(currentBounds));
+        exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: exit fade out animation: end", TAG);
+                startTransaction.apply();
+                mPipMenuController.detach();
+            }
+        });
+
+        final ValueAnimator exitFadeInAnimator = createAnimator();
+        exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+        exitFadeInAnimator.setDuration(mExitFadeInDuration);
+        exitFadeInAnimator.addUpdateListener(
+                animationUpdateListener(leash)
+                        .fadingIn()
+                        .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds));
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playSequentially(
+                exitFadeOutAnimator,
+                exitFadeInAnimator
+        );
+
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: exit animation: start", TAG);
+                mPipTransitionState.setTransitionState(EXITING_PIP);
+                sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: exit animation: cancel", TAG);
+                sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: exit animation: end", TAG);
+                mPipOrganizer.onExitPipFinished(taskInfo);
+
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+                wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+                wct.setBounds(taskInfo.token, destinationBounds);
+                finishCallback.onTransitionFinished(wct);
+
+                mPipTransitionState.setTransitionState(UNDEFINED);
+                sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
+
+                mCurrentAnimator = null;
+            }
+        });
+
+        animatorSet.start();
+        mCurrentAnimator = animatorSet;
+    }
+
+    @NonNull
+    private ValueAnimator createAnimator() {
+        final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
+        return animator;
+    }
+
+    @NonNull
+    private TvPipTransitionAnimatorUpdateListener animationUpdateListener(
+            @NonNull SurfaceControl leash) {
+        return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController,
+                mTransactionFactory.getTransaction(), mSurfaceTransactionHelper);
+    }
+
+    @NonNull
+    private static Rect scaledRect(@NonNull Rect rect, float scale) {
+        final Rect out = new Rect(rect);
+        out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2));
+        return out;
+    }
+
+    private boolean isCloseTransition(TransitionInfo info) {
+        final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+        return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE;
+    }
+
+    @Nullable
+    private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) {
+        if (mCurrentPipTaskToken == null) {
+            return null;
+        }
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (mCurrentPipTaskToken.equals(change.getContainer())) {
+                return change;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Whether we should handle the given {@link TransitionInfo} animation as entering PIP.
+     */
+    private boolean isEnteringPip(@NonNull TransitionInfo info) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (isEnteringPip(change, info.getType())) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Whether a particular change is a window that is entering pip.
+     */
+    @Override
+    public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+            @WindowManager.TransitionType int transitType) {
+        if (change.getTaskInfo() != null
+                && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+                && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) {
+            if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN
+                    || transitType == TRANSIT_CHANGE) {
+                return true;
+            }
+            // Please file a bug to handle the unexpected transition type.
+            android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+                    + transitTypeToString(transitType), new Throwable());
+        }
+        return false;
+    }
+
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.end();
+        }
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        if (requestHasPipEnter(request)) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: handle PiP enter request", TAG);
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            augmentRequest(transition, request, wct);
+            return wct;
+        } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null
+                && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) {
+            // if we receive a TRANSIT_TO_BACK type of request while in PiP
+            mPendingExitTransition = transition;
+
+            // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls
+            mPipTransitionState.setTransitionState(EXITING_PIP);
+
+            // return an empty WindowContainerTransaction so that we don't check other handlers
+            return new WindowContainerTransaction();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+            @NonNull WindowContainerTransaction outWCT) {
+        if (!requestHasPipEnter(request)) {
+            throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
+        }
+        outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED);
+    }
+
+    /**
+     * Cancel any ongoing PiP transitions/animations.
+     */
+    public void cancelAnimations() {
+        if (mPipAnimationController.isAnimating()) {
+            mPipAnimationController.getCurrentAnimator().cancel();
+            mPipAnimationController.resetAnimatorState();
+        }
+        if (mCurrentAnimator != null) {
+            mCurrentAnimator.cancel();
+        }
+    }
+
+    @Override
+    public void end() {
+        if (mCurrentAnimator != null) {
+            mCurrentAnimator.end();
+        }
+    }
+
+    private static class TvPipTransitionAnimatorUpdateListener implements
+            ValueAnimator.AnimatorUpdateListener {
+        private final SurfaceControl mLeash;
+        private final TvPipMenuController mTvPipMenuController;
+        private final SurfaceControl.Transaction mTransaction;
+        private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+        private final RectF mTmpRectF = new RectF();
+        private final Rect mTmpRect = new Rect();
+
+        private float mStartAlpha = ALPHA_NO_CHANGE;
+        private float mEndAlpha = ALPHA_NO_CHANGE;
+
+        @Nullable
+        private Rect mStartBounds;
+        @Nullable
+        private Rect mEndBounds;
+        private Rect mWindowContainerBounds;
+        private boolean mShowMenu;
+
+        TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash,
+                @NonNull TvPipMenuController tvPipMenuController,
+                @NonNull SurfaceControl.Transaction transaction,
+                @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+            mLeash = leash;
+            mTvPipMenuController = tvPipMenuController;
+            mTransaction = transaction;
+            mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+        }
+
+        public TvPipTransitionAnimatorUpdateListener animateAlpha(
+                @FloatRange(from = 0.0, to = 1.0) float startAlpha,
+                @FloatRange(from = 0.0, to = 1.0) float endAlpha) {
+            mStartAlpha = startAlpha;
+            mEndAlpha = endAlpha;
+            return this;
+        }
+
+        public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds,
+                @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) {
+            mStartBounds = startBounds;
+            mEndBounds = endBounds;
+            mWindowContainerBounds = windowContainerBounds;
+            return this;
+        }
+
+        public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) {
+            return animateBounds(bounds, bounds, bounds);
+        }
+
+        public TvPipTransitionAnimatorUpdateListener fadingOut() {
+            return animateAlpha(1f, 0f);
+        }
+
+        public TvPipTransitionAnimatorUpdateListener fadingIn() {
+            return animateAlpha(0f, 1f);
+        }
+
+        public TvPipTransitionAnimatorUpdateListener withMenu() {
+            mShowMenu = true;
+            return this;
+        }
+
+        @Override
+        public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+            final float fraction = animation.getAnimatedFraction();
+            final float alpha = lerp(mStartAlpha, mEndAlpha, fraction);
+            if (mStartBounds != null && mEndBounds != null) {
+                lerp(mStartBounds, mEndBounds, fraction, mTmpRectF);
+                applyAnimatedValue(alpha, mTmpRectF);
+            } else {
+                applyAnimatedValue(alpha, null);
+            }
+        }
+
+        private void applyAnimatedValue(float alpha, @Nullable RectF bounds) {
+            Trace.beginSection("applyAnimatedValue");
+            final SurfaceControl.Transaction tx = mTransaction;
+
+            Trace.beginSection("leash scale and alpha");
+            if (alpha != ALPHA_NO_CHANGE) {
+                mSurfaceTransactionHelper.alpha(tx, mLeash, alpha);
+            }
+            if (bounds != null) {
+                mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds);
+            }
+            mSurfaceTransactionHelper.shadow(tx, mLeash, false);
+            tx.show(mLeash);
+            Trace.endSection();
+
+            if (mShowMenu) {
+                Trace.beginSection("movePipMenu");
+                if (bounds != null) {
+                    mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right,
+                            (int) bounds.bottom);
+                    mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha);
+                } else {
+                    mTvPipMenuController.movePipMenu(tx, null, alpha);
+                }
+                Trace.endSection();
+            } else {
+                mTvPipMenuController.movePipMenu(tx, null, 0f);
+            }
+
+            tx.apply();
+            Trace.endSection();
+        }
+
+        private float lerp(float start, float end, float fraction) {
+            return start * (1 - fraction) + end * fraction;
+        }
+
+        private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction,
+                @NonNull RectF out) {
+            out.set(
+                    start.left * (1 - fraction) + end.left * fraction,
+                    start.top * (1 - fraction) + end.top * fraction,
+                    start.right * (1 - fraction) + end.right * fraction,
+                    start.bottom * (1 - fraction) + end.bottom * fraction);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 9bb383f..0448d94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -57,11 +57,6 @@
     @Nullable
     private SurfaceControl mPinnedTaskLeash;
 
-    // the leash of the original task of the PiP activity;
-    // used to synchronize app drawings in the multi-activity case
-    @Nullable
-    private SurfaceControl mOriginalTaskLeash;
-
     /**
      * A temporary broadcast receiver to initiate exit PiP via expand.
      * This will later be modified to be triggered by the PiP menu.
@@ -95,10 +90,6 @@
         mPinnedTaskLeash = pinnedTaskLeash;
     }
 
-    void setOriginalTaskLeash(SurfaceControl originalTaskLeash) {
-        mOriginalTaskLeash = originalTaskLeash;
-    }
-
     void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
         mPipTaskToken = pipTaskToken;
     }
@@ -133,6 +124,5 @@
     void onExitPip() {
         mPipTaskToken = null;
         mPinnedTaskLeash = null;
-        mOriginalTaskLeash = null;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 7d3bd65..6200ea5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.pip2.phone;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
@@ -50,7 +49,7 @@
 public class PipTransition extends PipTransitionController {
     private static final String TAG = PipTransition.class.getSimpleName();
 
-    private PipScheduler mPipScheduler;
+    private final PipScheduler mPipScheduler;
     @Nullable
     private WindowContainerToken mPipTaskToken;
     @Nullable
@@ -168,14 +167,9 @@
             }
             mPipTaskToken = pipChange.getContainer();
 
-            // cache the PiP task token and the relevant leashes
+            // cache the PiP task token and leash
             mPipScheduler.setPipTaskToken(mPipTaskToken);
             mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
-            // check if we entered PiP from a multi-activity task and set the original task leash
-            final int lastParentTaskId = pipChange.getTaskInfo().lastParentTaskIdBeforePip;
-            final boolean isSingleActivity = lastParentTaskId == INVALID_TASK_ID;
-            mPipScheduler.setOriginalTaskLeash(isSingleActivity ? null :
-                    findChangeByTaskId(info, lastParentTaskId).getLeash());
 
             startTransaction.apply();
             finishCallback.onTransitionFinished(null);
@@ -201,17 +195,6 @@
         return null;
     }
 
-    @Nullable
-    private TransitionInfo.Change findChangeByTaskId(TransitionInfo info, int taskId) {
-        for (TransitionInfo.Change change : info.getChanges()) {
-            if (change.getTaskInfo() != null
-                    && change.getTaskInfo().taskId == taskId) {
-                return change;
-            }
-        }
-        return null;
-    }
-
     private void onExitPip() {
         mPipTaskToken = null;
         mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index c2f15f6..e6418f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -182,7 +182,7 @@
         try {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                     "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
-            mSession.remove(mWindow);
+            mSession.remove(mWindow.asBinder());
         } catch (RemoteException e) {
             // nothing
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index ef8393c..35a1fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -151,7 +151,14 @@
 
     @Override
     public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
-        runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
+        if (mHandler.getLooper().isCurrentThread()) {
+            // We can only use the transaction if it can updated synchronously, otherwise the tx
+            // will be applied immediately after but also used/updated on the view thread which
+            // will lead to a race and/or crash
+            runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
+        } else {
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 723a4a7..193a4fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -60,6 +60,7 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 
 import android.animation.Animator;
@@ -424,7 +425,8 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
+            final int type = getTransitionTypeFromInfo(info);
+            Animation a = loadAnimation(type, info, change, wallpaperTransit, isDreamTransition);
             if (a != null) {
                 if (isTask) {
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -660,12 +662,11 @@
     }
 
     @Nullable
-    private Animation loadAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, int wallpaperTransit,
-            boolean isDreamTransition) {
+    private Animation loadAnimation(@WindowManager.TransitionType int type,
+            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+            int wallpaperTransit, boolean isDreamTransition) {
         Animation a;
 
-        final int type = info.getType();
         final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
@@ -721,7 +722,7 @@
             return null;
         } else {
             a = loadAttributeAnimation(
-                    info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
+                    type, info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
         }
 
         if (a != null) {
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 b528089d15..5c02dbc 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.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.wm.shell.transition.Transitions.TransitionObserver;
 
@@ -61,9 +62,10 @@
             }
 
             final int mode = change.getMode();
+            final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
             if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
-                    && TransitionUtil.isOpenOrCloseMode(mode)) {
-                notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
+                    && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) {
+                notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture);
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index d07d2b7b6..b012d35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
@@ -45,6 +46,7 @@
 import android.graphics.Shader;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
 import android.window.ScreenCapture;
@@ -61,10 +63,10 @@
 
     /** Loads the animation that is defined through attribute id for the given transition. */
     @Nullable
-    public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
+    public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
+            @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, int wallpaperTransit,
             @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
-        final int type = info.getType();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
         final boolean enter = TransitionUtil.isOpeningType(changeMode);
@@ -186,6 +188,38 @@
         return options.getCustomActivityTransition(isOpen);
     }
 
+    /**
+     * Gets the final transition type from {@link TransitionInfo} for determining the animation.
+     */
+    public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
+        final int type = info.getType();
+        // If the info transition type is opening transition, iterate its changes to see if it
+        // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
+        if (type == TRANSIT_OPEN) {
+            boolean hasOpenTransit = false;
+            for (TransitionInfo.Change change : info.getChanges()) {
+                if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
+                        && !TransitionUtil.isOrderOnly(change)) {
+                    // This isn't an activity-level transition.
+                    return type;
+                }
+                if (change.getTaskInfo() != null
+                        && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
+                    // Ignore non-activity containers.
+                    continue;
+                }
+                if (change.getMode() == TRANSIT_OPEN) {
+                    hasOpenTransit = true;
+                    break;
+                }
+            }
+            if (!hasOpenTransit) {
+                return TRANSIT_CLOSE;
+            }
+        }
+        return type;
+    }
+
     static Animation loadCustomActivityTransition(
             @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
             TransitionInfo.AnimationOptions options, boolean enter,
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 41ec33c..b98762d 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
@@ -654,12 +654,27 @@
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
                 info.getDebugId(), transitionToken, info);
-        final int activeIdx = findByToken(mPendingTransitions, transitionToken);
+        int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
-            throw new IllegalStateException("Got transitionReady for non-pending transition "
+            final ActiveTransition existing = getKnownTransition(transitionToken);
+            if (existing != null) {
+                Log.e(TAG, "Got duplicate transitionReady for " + transitionToken);
+                // The transition is already somewhere else in the pipeline, so just return here.
+                t.apply();
+                existing.mFinishT.merge(finishT);
+                return;
+            }
+            // This usually means the system is in a bad state and may not recover; however,
+            // there's an incentive to propagate bad states rather than crash, so we're kinda
+            // required to do the same thing I guess.
+            Log.wtf(TAG, "Got transitionReady for non-pending transition "
                     + transitionToken + ". expecting one of "
                     + Arrays.toString(mPendingTransitions.stream().map(
                             activeTransition -> activeTransition.mToken).toArray()));
+            final ActiveTransition fallback = new ActiveTransition();
+            fallback.mToken = transitionToken;
+            mPendingTransitions.add(fallback);
+            activeIdx = mPendingTransitions.size() - 1;
         }
         // Move from pending to ready
         final ActiveTransition active = mPendingTransitions.remove(activeIdx);
@@ -1050,34 +1065,43 @@
         processReadyQueue(track);
     }
 
-    private boolean isTransitionKnown(IBinder token) {
+    /**
+     * Checks to see if the transition specified by `token` is already known. If so, it will be
+     * returned.
+     */
+    @Nullable
+    private ActiveTransition getKnownTransition(IBinder token) {
         for (int i = 0; i < mPendingTransitions.size(); ++i) {
-            if (mPendingTransitions.get(i).mToken == token) return true;
+            final ActiveTransition active = mPendingTransitions.get(i);
+            if (active.mToken == token) return active;
         }
         for (int i = 0; i < mReadyDuringSync.size(); ++i) {
-            if (mReadyDuringSync.get(i).mToken == token) return true;
+            final ActiveTransition active = mReadyDuringSync.get(i);
+            if (active.mToken == token) return active;
         }
         for (int t = 0; t < mTracks.size(); ++t) {
             final Track tr = mTracks.get(t);
             for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
-                if (tr.mReadyTransitions.get(i).mToken == token) return true;
+                final ActiveTransition active = tr.mReadyTransitions.get(i);
+                if (active.mToken == token) return active;
             }
             final ActiveTransition active = tr.mActiveTransition;
             if (active == null) continue;
-            if (active.mToken == token) return true;
+            if (active.mToken == token) return active;
             if (active.mMerged == null) continue;
             for (int m = 0; m < active.mMerged.size(); ++m) {
-                if (active.mMerged.get(m).mToken == token) return true;
+                final ActiveTransition merged = active.mMerged.get(m);
+                if (merged.mToken == token) return merged;
             }
         }
-        return false;
+        return null;
     }
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
                 request.getDebugId(), transitionToken, request);
-        if (isTransitionKnown(transitionToken)) {
+        if (getKnownTransition(transitionToken) != null) {
             throw new RuntimeException("Transition already started " + transitionToken);
         }
         final ActiveTransition active = new ActiveTransition();
@@ -1161,7 +1185,7 @@
      */
     private void finishForSync(ActiveTransition reason,
             int trackIdx, @Nullable ActiveTransition forceFinish) {
-        if (!isTransitionKnown(reason.mToken)) {
+        if (getKnownTransition(reason.mToken) == null) {
             Log.d(TAG, "finishForSleep: already played sync transition " + reason);
             return;
         }
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 aff35a3..c12ac8b 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
@@ -149,7 +149,8 @@
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
-                    mSurfaceControlTransactionSupplier);
+                    mSurfaceControlTransactionSupplier,
+                    mDisplayController);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3add6f4..03006f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -428,7 +428,8 @@
                 if (isTaskInSplitScreen(mTaskId)) {
                     mSplitScreenController.moveTaskToFullscreen(mTaskId);
                 } else {
-                    mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
+                    mDesktopTasksController.ifPresent(c ->
+                            c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
@@ -536,6 +537,11 @@
             if (mGestureDetector.onTouchEvent(e)) {
                 return true;
             }
+            if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+                // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
+                // performed.
+                mShouldClick = false;
+            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
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 eba1a36..6ec91e0 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
@@ -293,7 +293,8 @@
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
-                    mSurfaceControlTransactionSupplier);
+                    mSurfaceControlTransactionSupplier,
+                    mDisplayController);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -547,8 +548,10 @@
      */
     private PointF offsetCaptionLocation(MotionEvent ev) {
         final PointF result = new PointF(ev.getX(), ev.getY());
-        final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
-                .positionInParent;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
+        if (taskInfo == null) return result;
+        final Point positionInParent = taskInfo.positionInParent;
         result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
         result.offset(-positionInParent.x, -positionInParent.y);
         return result;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 1669cf4..8ce2d6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -40,8 +40,9 @@
      *                 {@code 0} to indicate it's a move
      * @param x x coordinate in window decoration coordinate system where the drag starts
      * @param y y coordinate in window decoration coordinate system where the drag starts
+     * @return the starting task bounds
      */
-    void onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
+    Rect onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
 
     /**
      * Called when the pointer moves during a drag-resize or drag-move.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 518f4b8..53ec201 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,7 +49,8 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.view.BaseIWindow;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 
 import java.util.function.Supplier;
 
@@ -68,7 +69,9 @@
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
-    private final BaseIWindow mFakeWindow;
+
+    private final IBinder mClientToken;
+
     private final IBinder mFocusGrantToken;
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
@@ -76,8 +79,9 @@
     private final DragPositioningCallback mCallback;
 
     private final SurfaceControl mInputSinkSurface;
-    private final BaseIWindow mFakeSinkWindow;
+    private final IBinder mSinkClientToken;
     private final InputChannel mSinkInputChannel;
+    private final DisplayController mDisplayController;
 
     private int mTaskWidth;
     private int mTaskHeight;
@@ -92,6 +96,7 @@
 
     private int mDragPointerId = -1;
     private DragDetector mDragDetector;
+    private final Region mTouchRegion = new Region();
 
     DragResizeInputListener(
             Context context,
@@ -102,7 +107,8 @@
             SurfaceControl decorationSurface,
             DragPositioningCallback callback,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
-            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+            DisplayController displayController) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
@@ -110,17 +116,15 @@
         mDisplayId = displayId;
         mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
-        // Use a fake window as the backing surface is a container layer, and we don't want to
-        // create a buffer layer for it, so we can't use ViewRootImpl.
-        mFakeWindow = new BaseIWindow();
-        mFakeWindow.setSession(mWindowSession);
+        mDisplayController = displayController;
+        mClientToken = new Binder();
         mFocusGrantToken = new Binder();
         mInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
                     mDisplayId,
                     mDecorationSurface,
-                    mFakeWindow.asBinder(),
+                    mClientToken,
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
@@ -149,13 +153,13 @@
                 .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
                 .show(mInputSinkSurface)
                 .apply();
-        mFakeSinkWindow = new BaseIWindow();
+        mSinkClientToken = new Binder();
         mSinkInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
                     mDisplayId,
                     mInputSinkSurface,
-                    mFakeSinkWindow.asBinder(),
+                    mSinkClientToken,
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     0 /* privateFlags */,
@@ -195,34 +199,34 @@
         mCornerSize = cornerSize;
         mDragDetector.setTouchSlop(touchSlop);
 
-        Region touchRegion = new Region();
+        mTouchRegion.setEmpty();
         final Rect topInputBounds = new Rect(
                 -mResizeHandleThickness,
                 -mResizeHandleThickness,
                 mTaskWidth + mResizeHandleThickness,
                 0);
-        touchRegion.union(topInputBounds);
+        mTouchRegion.union(topInputBounds);
 
         final Rect leftInputBounds = new Rect(
                 -mResizeHandleThickness,
                 0,
                 0,
                 mTaskHeight);
-        touchRegion.union(leftInputBounds);
+        mTouchRegion.union(leftInputBounds);
 
         final Rect rightInputBounds = new Rect(
                 mTaskWidth,
                 0,
                 mTaskWidth + mResizeHandleThickness,
                 mTaskHeight);
-        touchRegion.union(rightInputBounds);
+        mTouchRegion.union(rightInputBounds);
 
         final Rect bottomInputBounds = new Rect(
                 -mResizeHandleThickness,
                 mTaskHeight,
                 mTaskWidth + mResizeHandleThickness,
                 mTaskHeight + mResizeHandleThickness);
-        touchRegion.union(bottomInputBounds);
+        mTouchRegion.union(bottomInputBounds);
 
         // Set up touch areas in each corner.
         int cornerRadius = mCornerSize / 2;
@@ -231,28 +235,28 @@
                 -cornerRadius,
                 cornerRadius,
                 cornerRadius);
-        touchRegion.union(mLeftTopCornerBounds);
+        mTouchRegion.union(mLeftTopCornerBounds);
 
         mRightTopCornerBounds = new Rect(
                 mTaskWidth - cornerRadius,
                 -cornerRadius,
                 mTaskWidth + cornerRadius,
                 cornerRadius);
-        touchRegion.union(mRightTopCornerBounds);
+        mTouchRegion.union(mRightTopCornerBounds);
 
         mLeftBottomCornerBounds = new Rect(
                 -cornerRadius,
                 mTaskHeight - cornerRadius,
                 cornerRadius,
                 mTaskHeight + cornerRadius);
-        touchRegion.union(mLeftBottomCornerBounds);
+        mTouchRegion.union(mLeftBottomCornerBounds);
 
         mRightBottomCornerBounds = new Rect(
                 mTaskWidth - cornerRadius,
                 mTaskHeight - cornerRadius,
                 mTaskWidth + cornerRadius,
                 mTaskHeight + cornerRadius);
-        touchRegion.union(mRightBottomCornerBounds);
+        mTouchRegion.union(mRightBottomCornerBounds);
 
         try {
             mWindowSession.updateInputChannel(
@@ -262,7 +266,7 @@
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
                     INPUT_FEATURE_SPY,
-                    touchRegion);
+                    mTouchRegion);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -281,19 +285,8 @@
         // issue. However, were there touchscreen-only a region out of the task bounds, mouse
         // gestures will become no-op in that region, even though the mouse gestures may appear to
         // be performed on the input window behind the resize handle.
-        touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
-        try {
-            mWindowSession.updateInputChannel(
-                    mSinkInputChannel.getToken(),
-                    mDisplayId,
-                    mInputSinkSurface,
-                    FLAG_NOT_FOCUSABLE,
-                    0 /* privateFlags */,
-                    INPUT_FEATURE_NO_INPUT_CHANNEL,
-                    touchRegion);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
+        mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+        updateSinkInputChannel(mTouchRegion);
         return true;
     }
 
@@ -309,19 +302,34 @@
         return region;
     }
 
+    private void updateSinkInputChannel(Region region) {
+        try {
+            mWindowSession.updateInputChannel(
+                    mSinkInputChannel.getToken(),
+                    mDisplayId,
+                    mInputSinkSurface,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
+                    region);
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     public void close() {
         mInputEventReceiver.dispose();
         mInputChannel.dispose();
         try {
-            mWindowSession.remove(mFakeWindow);
+            mWindowSession.remove(mClientToken);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
 
         mSinkInputChannel.dispose();
         try {
-            mWindowSession.remove(mFakeSinkWindow);
+            mWindowSession.remove(mSinkClientToken);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -337,6 +345,7 @@
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
         private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
+        private Rect mDragStartTaskBounds;
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -398,12 +407,15 @@
                     }
                     if (mShouldHandleEvents) {
                         mInputManager.pilferPointers(mInputChannel.getToken());
-
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
                         int ctrlType = calculateCtrlType(isTouch, x, y);
-                        mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
+                        mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
+                                rawX, rawY);
+                        // Increase the input sink region to cover the whole screen; this is to
+                        // prevent input and focus from going to other tasks during a drag resize.
+                        updateInputSinkRegionForDrag(mDragStartTaskBounds);
                         result = true;
                     }
                     break;
@@ -415,7 +427,8 @@
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
-                    mCallback.onDragPositioningMove(rawX, rawY);
+                    final Rect taskBounds = mCallback.onDragPositioningMove(rawX, rawY);
+                    updateInputSinkRegionForDrag(taskBounds);
                     result = true;
                     break;
                 }
@@ -423,8 +436,13 @@
                 case MotionEvent.ACTION_CANCEL: {
                     if (mShouldHandleEvents) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
-                        mCallback.onDragPositioningEnd(
+                        final Rect taskBounds = mCallback.onDragPositioningEnd(
                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+                        // If taskBounds has changed, setGeometry will be called and update the
+                        // sink region. Otherwise, we should revert it here.
+                        if (taskBounds.equals(mDragStartTaskBounds)) {
+                            updateSinkInputChannel(mTouchRegion);
+                        }
                     }
                     mShouldHandleEvents = false;
                     mDragPointerId = -1;
@@ -444,6 +462,18 @@
             return result;
         }
 
+        private void updateInputSinkRegionForDrag(Rect taskBounds) {
+            final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+            final Region dragTouchRegion = new Region(-taskBounds.left,
+                    -taskBounds.top,
+                    -taskBounds.left + layout.width(),
+                    -taskBounds.top + layout.height());
+            // Remove the localized task bounds from the touch region.
+            taskBounds.offsetTo(0, 0);
+            dragTouchRegion.op(taskBounds, Region.Op.DIFFERENCE);
+            updateSinkInputChannel(dragTouchRegion);
+        }
+
         private boolean isInCornerBounds(float xf, float yf) {
             return calculateCornersCtrlType(xf, yf) != 0;
         }
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 dadd264..bf11c8b 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
@@ -68,7 +68,7 @@
     }
 
     @Override
-    public void onDragPositioningStart(int ctrlType, float x, float y) {
+    public Rect onDragPositioningStart(int ctrlType, float x, float y) {
         mCtrlType = ctrlType;
         mTaskBoundsAtDragStart.set(
                 mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -87,6 +87,7 @@
             mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
+        return mRepositionTaskBounds;
     }
 
     @Override
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 852c037..79fec09 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
@@ -85,7 +85,7 @@
     }
 
     @Override
-    public void onDragPositioningStart(int ctrlType, float x, float y) {
+    public Rect onDragPositioningStart(int ctrlType, float x, float y) {
         mCtrlType = ctrlType;
         mTaskBoundsAtDragStart.set(
                 mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -107,6 +107,7 @@
             mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
+        return mRepositionTaskBounds;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
index 744e8c2..181474f 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -57,25 +57,18 @@
         resetLetterboxStyle()
         _letterboxStyle = mapLetterboxStyle()
         val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
-        var hasLetterboxEducationStateChanged = false
         if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
-            hasLetterboxEducationStateChanged = true
             execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled)
         }
-        return try {
-            object : Statement() {
-                @Throws(Throwable::class)
-                override fun evaluate() {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                try {
                     base!!.evaluate()
+                } finally {
+                    resetLetterboxStyle()
                 }
             }
-        } finally {
-            if (hasLetterboxEducationStateChanged) {
-                execAdb(
-                    "wm set-letterbox-style --isEducationEnabled " + isLetterboxEducationEnabled
-                )
-            }
-            resetLetterboxStyle()
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
index 406ada9..5a017ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
       atrace_categories: "sched_process_exit"
       atrace_apps: "com.android.server.wm.flicker.testapp"
       atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
       atrace_apps: "com.android.wm.shell.flicker.other"
-      atrace_apps: "com.android.wm.shell.flicker.bubbles"
-      atrace_apps: "com.android.wm.shell.flicker.pip"
-      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
       atrace_apps: "com.google.android.apps.nexuslauncher"
     }
   }
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
index 406ada9..1599831 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
       atrace_categories: "sched_process_exit"
       atrace_apps: "com.android.server.wm.flicker.testapp"
       atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
-      atrace_apps: "com.android.wm.shell.flicker.other"
       atrace_apps: "com.android.wm.shell.flicker.bubbles"
-      atrace_apps: "com.android.wm.shell.flicker.pip"
-      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
       atrace_apps: "com.google.android.apps.nexuslauncher"
     }
   }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index 89ecc29..fafd37b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -45,12 +45,15 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="run-command" value="settings put global package_verifier_user_consent -1"/>
         <option name="teardown-command"
                 value="settings delete secure show_ime_with_hard_keyboard"/>
         <option name="teardown-command" value="settings delete system show_touches"/>
         <option name="teardown-command" value="settings delete system pointer_location"/>
         <option name="teardown-command"
                 value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+        <option name="teardown-command"
+                value="settings put global package_verifier_user_consent 1"/>
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
@@ -76,6 +79,19 @@
                 value="appops set com.android.shell android:mock_location deny"/>
     </target_preparer>
 
+    <target_preparer class="com.android.csuite.core.AppCrawlTesterHostPreparer"/>
+
+    <!-- Use app crawler to log into Netflix -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command"
+                value="am start -n com.netflix.mediaclient/com.netflix.mediaclient.ui.login.LoginActivity"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="set-option" value="package-name:com.netflix.mediaclient"/>
+        <option name="set-option" value="ui-automator-mode:true"/>
+        <option name="class" value="com.android.csuite.tests.AppCrawlTest" />
+    </test>
+
     <!-- Needed for pushing the trace config file -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 42191d1..182a908 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -31,10 +31,18 @@
 abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
     protected abstract val standardAppHelper: StandardAppHelper
 
+    protected abstract val permissions: Array<String>
+
     @FlickerBuilderProvider
     override fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             instrumentation.uiAutomation.adoptShellPermissionIdentity()
+            for (permission in permissions) {
+                instrumentation.uiAutomation.grantRuntimePermission(
+                    standardAppHelper.packageName,
+                    permission
+                )
+            }
             setup { flicker.scenario.setIsTablet(tapl.isTablet) }
             transition()
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 4da52ef..d06cf77 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip.apps
 
+import android.Manifest
 import android.content.Context
 import android.location.Criteria
 import android.location.Location
@@ -64,6 +65,9 @@
 open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
     override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
 
+    override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS,
+        Manifest.permission.ACCESS_FINE_LOCATION)
+
     val locationManager: LocationManager =
         instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
     val mainHandler = Handler(Looper.getMainLooper())
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 5498e8c..32f1259 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip.apps
 
+import android.Manifest
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
@@ -62,6 +63,8 @@
 open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
     override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
 
+    override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
+
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
             standardAppHelper.launchViaIntent(
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index d8afc25..509b32c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip.apps
 
+import android.Manifest
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.YouTubeAppHelper
@@ -58,6 +59,8 @@
 open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
     override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
 
+    override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
+
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
             standardAppHelper.launchViaIntent(
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
index 406ada9..fc15ff9 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
       atrace_categories: "sched_process_exit"
       atrace_apps: "com.android.server.wm.flicker.testapp"
       atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
-      atrace_apps: "com.android.wm.shell.flicker.other"
-      atrace_apps: "com.android.wm.shell.flicker.bubbles"
       atrace_apps: "com.android.wm.shell.flicker.pip"
-      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
       atrace_apps: "com.google.android.apps.nexuslauncher"
     }
   }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
index 406ada9..9f2e497 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
       atrace_categories: "sched_process_exit"
       atrace_apps: "com.android.server.wm.flicker.testapp"
       atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
-      atrace_apps: "com.android.wm.shell.flicker.other"
-      atrace_apps: "com.android.wm.shell.flicker.bubbles"
-      atrace_apps: "com.android.wm.shell.flicker.pip"
-      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+      atrace_apps: "com.android.wm.shell.flicker.service"
       atrace_apps: "com.google.android.apps.nexuslauncher"
     }
   }
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index fdda597..05f937a 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -95,6 +95,8 @@
         <option name="pull-pattern-keys" value="perfetto_file_path"/>
         <option name="directory-keys"
                 value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
index 406ada9..67316d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
@@ -63,10 +63,7 @@
       atrace_categories: "sched_process_exit"
       atrace_apps: "com.android.server.wm.flicker.testapp"
       atrace_apps: "com.android.systemui"
-      atrace_apps: "com.android.wm.shell.flicker"
-      atrace_apps: "com.android.wm.shell.flicker.other"
-      atrace_apps: "com.android.wm.shell.flicker.bubbles"
-      atrace_apps: "com.android.wm.shell.flicker.pip"
+      atrace_apps: "com.android.wm.shell.flicker.service"
       atrace_apps: "com.android.wm.shell.flicker.splitscreen"
       atrace_apps: "com.google.android.apps.nexuslauncher"
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
new file mode 100644
index 0000000..b91d6f9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
@@ -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.wm.shell;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+
+/**
+ * Basic test handler that immediately executes anything that is posted on it.
+ */
+public class TestHandler extends Handler {
+    public TestHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+        dispatchMessage(msg);
+        return true;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index fde6acb..94c862b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -63,6 +63,7 @@
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE
 import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.google.common.truth.Truth.assertThat
@@ -392,8 +393,8 @@
     fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
-        controller.moveToFullscreen(task)
-        val wct = getLatestWct(type = TRANSIT_CHANGE)
+        controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+        val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_UNDEFINED)
     }
@@ -402,15 +403,15 @@
     fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
-        controller.moveToFullscreen(task)
-        val wct = getLatestWct(type = TRANSIT_CHANGE)
+        controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+        val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
 
     @Test
     fun moveToFullscreen_nonExistentTask_doesNothing() {
-        controller.moveToFullscreen(999)
+        controller.moveToFullscreen(999, desktopModeWindowDecoration)
         verifyWCTNotExecuted()
     }
 
@@ -419,9 +420,9 @@
         val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
 
-        controller.moveToFullscreen(taskDefaultDisplay)
+        controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
 
-        with(getLatestWct(type = TRANSIT_CHANGE)) {
+        with(getLatestExitDesktopWct()) {
             assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
             assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
         }
@@ -808,6 +809,17 @@
         return arg.value
     }
 
+    private fun getLatestExitDesktopWct(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        if (ENABLE_SHELL_TRANSITIONS) {
+            verify(exitDesktopTransitionHandler)
+                    .startTransition(eq(TRANSIT_EXIT_DESKTOP_MODE), arg.capture(), any(), any())
+        } else {
+            verify(shellTaskOrganizer).applyTransaction(arg.capture())
+        }
+        return arg.value
+    }
+
     private fun verifyWCTNotExecuted() {
         if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index a5629c8..3bc90ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -13,23 +13,26 @@
 import android.view.SurfaceControl
 import android.window.TransitionInfo
 import android.window.TransitionInfo.FLAG_IS_WALLPAPER
+import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import java.util.function.Supplier
+import junit.framework.Assert.assertFalse
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
@@ -113,6 +116,40 @@
     }
 
     @Test
+    fun startDragToDesktop_aborted_finishDropped() {
+        val task = createTask()
+        val dragAnimator = mock<MoveToDesktopAnimator>()
+        // Simulate transition is started.
+        val transition = startDragToDesktopTransition(task, dragAnimator)
+        // But the transition was aborted.
+        handler.onTransitionConsumed(transition, aborted = true, mock())
+
+        // Attempt to finish the failed drag start.
+        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+        // Should not be attempted and state should be reset.
+        verify(transitions, never())
+                .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+        assertFalse(handler.inProgress)
+    }
+
+    @Test
+    fun startDragToDesktop_aborted_cancelDropped() {
+        val task = createTask()
+        val dragAnimator = mock<MoveToDesktopAnimator>()
+        // Simulate transition is started.
+        val transition = startDragToDesktopTransition(task, dragAnimator)
+        // But the transition was aborted.
+        handler.onTransitionConsumed(transition, aborted = true, mock())
+
+        // Attempt to finish the failed drag start.
+        handler.cancelDragToDesktopTransition()
+
+        // Should not be attempted and state should be reset.
+        assertFalse(handler.inProgress)
+    }
+
+    @Test
     fun cancelDragToDesktop_startWasReady_cancel() {
         val task = createTask()
         val dragAnimator = mock<MoveToDesktopAnimator>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 4e2b7f6..800f9e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -66,6 +66,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -283,7 +284,7 @@
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
                 .round(any(), any(), anyBoolean());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
-                .scale(any(), any(), any(), any(), anyFloat());
+                .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
                 .alpha(any(), any(), anyFloat());
         doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 4afb29e..d7c4610 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -58,6 +59,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestHandler;
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
@@ -92,8 +94,7 @@
     Transitions mTransitions;
     @Mock
     Looper mViewLooper;
-    @Mock
-    Handler mViewHandler;
+    TestHandler mViewHandler;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -112,7 +113,7 @@
 
         mContext = getContext();
         doReturn(true).when(mViewLooper).isCurrentThread();
-        doReturn(mViewLooper).when(mViewHandler).getLooper();
+        mViewHandler = spy(new TestHandler(mViewLooper));
 
         mTaskInfo = new ActivityManager.RunningTaskInfo();
         mTaskInfo.token = mToken;
@@ -668,4 +669,24 @@
         mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
         verify(mViewHandler).post(any());
     }
+
+    @Test
+    public void testSetResizeBgOnSameUiThread_expectUsesTransaction() {
+        SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+        mTaskView = spy(mTaskView);
+        mTaskView.setResizeBgColor(tx, Color.BLUE);
+        verify(mViewHandler, never()).post(any());
+        verify(mTaskView, never()).setResizeBackgroundColor(eq(Color.BLUE));
+        verify(mTaskView).setResizeBackgroundColor(eq(tx), eq(Color.BLUE));
+    }
+
+    @Test
+    public void testSetResizeBgOnDifferentUiThread_expectDoesNotUseTransaction() {
+        doReturn(false).when(mViewLooper).isCurrentThread();
+        SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+        mTaskView = spy(mTaskView);
+        mTaskView.setResizeBgColor(tx, Color.BLUE);
+        verify(mViewHandler).post(any());
+        verify(mTaskView).setResizeBackgroundColor(eq(Color.BLUE));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index ea7c0d9..421c445 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -18,8 +18,10 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
@@ -145,6 +147,26 @@
         verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
     }
 
+    @Test
+    public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
+        TransitionInfo info = mock(TransitionInfo.class);
+        TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+        ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+        when(change.getTaskInfo()).thenReturn(taskInfo);
+        when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+        when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true);
+        setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE);
+
+        mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+                info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class));
+
+        verify(mListener, times(1)).onHomeVisibilityChanged(true);
+    }
+
+
     /**
      * Helper class to initialize variables for the rest.
      */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 4e300d9..01c9bd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -40,6 +40,8 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -93,6 +95,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -1463,6 +1467,43 @@
         assertEquals(0, mDefaultHandler.activeCount());
     }
 
+    @Test
+    public void testCloseTransitAnimationWhenClosingChangesExists() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+        final TransitionAnimation transitionAnimation = new TransitionAnimation(mContext, false,
+                Transitions.TAG);
+        spyOn(transitionAnimation);
+
+        // Creating a transition by the app hooking the back key event to start the
+        // previous activity with FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+        // flags in order to clear the top activity and bring the exist previous activity to front.
+        // Expects the activity transition should playing the close animation instead the initiated
+        // open animation made by startActivity.
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_CLOSE).addChange(TRANSIT_TO_FRONT).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        final int type = getTransitionTypeFromInfo(info);
+        assertEquals(TRANSIT_CLOSE, type);
+
+        TransitionAnimationHelper.loadAttributeAnimation(type, info, info.getChanges().get(0), 0,
+                transitionAnimation, false);
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+                eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean());
+
+        TransitionAnimationHelper.loadAttributeAnimation(type, info, info.getChanges().get(1), 0,
+                transitionAnimation, false);
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+                eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean());
+    }
+
     class ChangeBuilder {
         final TransitionInfo.Change mChange;
 
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index d056248..8748dab 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -603,7 +603,7 @@
     std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode);
     if (asset) {
       if (out_cookie != nullptr) {
-        *out_cookie = i;
+        *out_cookie = i - 1;
       }
       return asset;
     }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index da728f9..79a7357 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -553,6 +553,7 @@
         "utils/Blur.cpp",
         "utils/Color.cpp",
         "utils/LinearAllocator.cpp",
+        "utils/TypefaceUtils.cpp",
         "utils/VectorDrawableUtils.cpp",
         "AnimationContext.cpp",
         "Animator.cpp",
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index cd4fae8..b667daf 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -80,6 +80,19 @@
 static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
     if (transform == ColorTransform::None) return;
 
+    if (transform == ColorTransform::Invert) {
+        auto filter = SkHighContrastFilter::Make(
+                {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
+                 /* contrast= */ 0.0f});
+
+        if (paint.getColorFilter()) {
+            paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+        } else {
+            paint.setColorFilter(filter);
+        }
+        return;
+    }
+
     SkColor newColor = transformColor(transform, paint.getColor());
     paint.setColor(newColor);
 
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index 291f4cf..288dca4 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -29,12 +29,15 @@
     Unknown = 0,
     Background = 1,
     Foreground = 2,
+    // Contains foreground (usually text), like a button or chip
+    Container = 3
 };
 
 enum class ColorTransform {
     None,
     Light,
     Dark,
+    Invert
 };
 
 // True if the paint was modified, false otherwise
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 8c180da..b1c5bf4 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -145,6 +145,8 @@
         return mImpl && mImpl->hasText();
     }
 
+    [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); }
+
     void applyColorTransform(ColorTransform transform) {
         if (mImpl) {
             mImpl->applyColorTransform(transform);
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index ff0d8d74..3b694c5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -718,6 +718,27 @@
     return (value & (value - 1)) == 0;
 }
 
+template <typename T>
+constexpr bool doesPaintHaveFill(T& paint) {
+    using T1 = std::remove_cv_t<T>;
+    if constexpr (std::is_same_v<T1, SkPaint>) {
+        return paint.getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, SkPaint&>) {
+        return paint.getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, SkPaint*>) {
+        return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, const SkPaint*>) {
+        return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+    }
+
+    return false;
+}
+
+template <typename... Args>
+constexpr bool hasPaintWithFill(Args&&... args) {
+    return (... || doesPaintHaveFill(args));
+}
+
 template <typename T, typename... Args>
 void* DisplayListData::push(size_t pod, Args&&... args) {
     size_t skip = SkAlignPtr(sizeof(T) + pod);
@@ -736,6 +757,14 @@
     new (op) T{std::forward<Args>(args)...};
     op->type = (uint32_t)T::kType;
     op->skip = skip;
+
+    // check if this is a fill op or not, in case we need to avoid messing with it with force invert
+    if constexpr (!std::is_same_v<T, DrawTextBlob>) {
+        if (hasPaintWithFill(args...)) {
+            mHasFill = true;
+        }
+    }
+
     return op + 1;
 }
 
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 4f54ee2..afadbfd 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -110,7 +110,7 @@
 
 class DisplayListData final {
 public:
-    DisplayListData() : mHasText(false) {}
+    DisplayListData() : mHasText(false), mHasFill(false) {}
     ~DisplayListData();
 
     void draw(SkCanvas* canvas) const;
@@ -121,6 +121,7 @@
     void applyColorTransform(ColorTransform transform);
 
     bool hasText() const { return mHasText; }
+    bool hasFill() const { return mHasFill; }
     size_t usedSize() const { return fUsed; }
     size_t allocatedSize() const { return fReserved; }
 
@@ -192,6 +193,7 @@
     size_t fReserved = 0;
 
     bool mHasText : 1;
+    bool mHasFill : 1;
 };
 
 class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 3e131bc..0b42c88 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -427,7 +427,13 @@
         children.push_back(node);
     });
     if (mDisplayList.hasText()) {
-        usage = UsageHint::Foreground;
+        if (mDisplayList.hasFill()) {
+            // Handle a special case for custom views that draw both text and background in the
+            // same RenderNode, which would otherwise be altered to white-on-white text.
+            usage = UsageHint::Container;
+        } else {
+            usage = UsageHint::Foreground;
+        }
     }
     if (usage == UsageHint::Unknown) {
         if (children.size() > 1) {
@@ -453,8 +459,13 @@
             drawn.join(bounds);
         }
     }
-    mDisplayList.applyColorTransform(
-            usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
+
+    if (usage == UsageHint::Container) {
+        mDisplayList.applyColorTransform(ColorTransform::Invert);
+    } else {
+        mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark
+                                                                        : ColorTransform::Light);
+    }
 }
 
 void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 31fc929..008ea3a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -589,10 +589,40 @@
 // Canvas draw operations: Bitmaps
 // ----------------------------------------------------------------------------
 
+bool SkiaCanvas::useGainmapShader(Bitmap& bitmap) {
+    // If the bitmap doesn't have a gainmap, don't use the gainmap shader
+    if (!bitmap.hasGainmap()) return false;
+
+    // If we don't have an owned canvas, then we're either hardware accelerated or drawing
+    // to a picture - use the gainmap shader out of caution. Ideally a picture canvas would
+    // use a drawable here instead to defer making that decision until the last possible
+    // moment
+    if (!mCanvasOwned) return true;
+
+    auto info = mCanvasOwned->imageInfo();
+
+    // If it's an unknown colortype then it's not a bitmap-backed canvas
+    if (info.colorType() == SkColorType::kUnknown_SkColorType) return true;
+
+    skcms_TransferFunction tfn;
+    info.colorSpace()->transferFn(&tfn);
+
+    auto transferType = skcms_TransferFunction_getType(&tfn);
+    switch (transferType) {
+        case skcms_TFType_HLGish:
+        case skcms_TFType_HLGinvish:
+        case skcms_TFType_PQish:
+            return true;
+        case skcms_TFType_Invalid:
+        case skcms_TFType_sRGBish:
+            return false;
+    }
+}
+
 void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
     auto image = bitmap.makeImage();
 
-    if (bitmap.hasGainmap()) {
+    if (useGainmapShader(bitmap)) {
         Paint gainmapPaint = paint ? *paint : Paint();
         sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
                 image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
@@ -619,7 +649,7 @@
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
-    if (bitmap.hasGainmap()) {
+    if (useGainmapShader(bitmap)) {
         Paint gainmapPaint = paint ? *paint : Paint();
         sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
                 image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index ced0224..4bf1790 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -223,6 +223,8 @@
 
     void drawPoints(const float* points, int count, const Paint& paint, SkCanvas::PointMode mode);
 
+    bool useGainmapShader(Bitmap& bitmap);
+
     class Clip;
 
     std::unique_ptr<SkCanvas> mCanvasOwned;  // Might own a canvas we allocated.
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 34cb4ae..f4ee36ec 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,6 +30,7 @@
 #include <minikin/MinikinExtent.h>
 #include <minikin/MinikinPaint.h>
 #include <minikin/MinikinRect.h>
+#include <utils/TypefaceUtils.h>
 
 namespace android {
 
@@ -142,7 +143,7 @@
         skVariation[i].value = SkFloatToScalar(variations[i].value);
     }
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
 
     return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index caffdfc..ef4dce5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,18 +17,18 @@
 #ifndef ANDROID_GRAPHICS_PAINT_H_
 #define ANDROID_GRAPHICS_PAINT_H_
 
-#include "Typeface.h"
-
-#include <cutils/compiler.h>
-
 #include <SkFont.h>
 #include <SkPaint.h>
 #include <SkSamplingOptions.h>
+#include <cutils/compiler.h>
+#include <minikin/FamilyVariant.h>
+#include <minikin/FontFamily.h>
+#include <minikin/FontFeature.h>
+#include <minikin/Hyphenator.h>
+
 #include <string>
 
-#include <minikin/FontFamily.h>
-#include <minikin/FamilyVariant.h>
-#include <minikin/Hyphenator.h>
+#include "Typeface.h"
 
 namespace android {
 
@@ -82,11 +82,15 @@
 
     float getWordSpacing() const { return mWordSpacing; }
 
-    void setFontFeatureSettings(const std::string& fontFeatureSettings) {
-        mFontFeatureSettings = fontFeatureSettings;
+    void setFontFeatureSettings(std::string_view fontFeatures) {
+        mFontFeatureSettings = minikin::FontFeature::parse(fontFeatures);
     }
 
-    std::string getFontFeatureSettings() const { return mFontFeatureSettings; }
+    void resetFontFeatures() { mFontFeatureSettings.clear(); }
+
+    const std::vector<minikin::FontFeature>& getFontFeatureSettings() const {
+        return mFontFeatureSettings;
+    }
 
     void setMinikinLocaleListId(uint32_t minikinLocaleListId) {
         mMinikinLocaleListId = minikinLocaleListId;
@@ -170,7 +174,7 @@
 
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
-    std::string mFontFeatureSettings;
+    std::vector<minikin::FontFeature> mFontFeatureSettings;
     uint32_t mMinikinLocaleListId;
     std::optional<minikin::FamilyVariant> mFamilyVariant;
     uint32_t mHyphenEdit = 0;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index b63ee1b..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
 
 #include "MinikinSkia.h"
 #include "SkPaint.h"
-#include "SkStream.h"  // Fot tests.
+#include "SkStream.h"  // For tests.
 #include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
 
 #include <minikin/FontCollection.h>
 #include <minikin/FontFamily.h>
@@ -186,7 +187,9 @@
     LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
     void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
-    sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+    LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+    sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
     std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 0c3af61..e6d790f 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -31,6 +31,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/LocaleList.h>
 #include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
 
 #include <memory>
 
@@ -125,7 +126,7 @@
     args.setCollectionIndex(ttcIndex);
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
 
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
     if (face == NULL) {
         ALOGE("addFont failed to create font, invalid request");
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 8c71d6f..d84b73d 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -33,6 +33,7 @@
 #include <cassert>
 #include <cstring>
 #include <memory>
+#include <string_view>
 #include <vector>
 
 #include "ColorFilter.h"
@@ -690,10 +691,11 @@
                                        jstring settings) {
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         if (!settings) {
-            paint->setFontFeatureSettings(std::string());
+            paint->resetFontFeatures();
         } else {
             ScopedUtfChars settingsChars(env, settings);
-            paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+            paint->setFontFeatureSettings(
+                    std::string_view(settingsChars.c_str(), settingsChars.size()));
         }
     }
 
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 4dbfa88..c55066a 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -332,7 +332,8 @@
 
 bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
         SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-        int width, int height, int jpegQuality) {
+        int width, int height, int jpegQuality, ScopedByteArrayRO* jExif,
+        ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) {
     // Check SDR color space. Now we only support SRGB transfer function
     if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) !=  ADataSpace::TRANSFER_SRGB) {
         jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
@@ -340,6 +341,19 @@
             "The requested SDR color space is not supported. Transfer function must be SRGB");
         return false;
     }
+    // Check HDR and SDR strides length.
+    // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)).
+    // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr).
+    if (jHdrStrides->size() != 2) {
+        jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2.");
+        return false;
+    }
+    if (jSdrStrides->size() != 3) {
+        jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3.");
+        return false;
+    }
 
     ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
     ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
@@ -351,20 +365,32 @@
         return false;
     }
 
+    const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get());
+    const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get());
+
     JpegR jpegREncoder;
 
     jpegr_uncompressed_struct p010;
     p010.data = hdr;
     p010.width = width;
     p010.height = height;
+    // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte.
+    p010.luma_stride = (hdrStrides[0] + 1) / 2;
+    p010.chroma_stride = (hdrStrides[1] + 1) / 2;
     p010.colorGamut = hdrColorGamut;
 
     jpegr_uncompressed_struct yuv420;
     yuv420.data = sdr;
     yuv420.width = width;
     yuv420.height = height;
+    yuv420.luma_stride = sdrStrides[0];
+    yuv420.chroma_stride = sdrStrides[1];
     yuv420.colorGamut = sdrColorGamut;
 
+    jpegr_exif_struct exif;
+    exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get()));
+    exif.length = jExif->size();
+
     jpegr_compressed_struct jpegR;
     jpegR.maxLength = width * height * sizeof(uint8_t);
 
@@ -373,7 +399,8 @@
 
     if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
             hdrTransferFunction,
-            &jpegR, jpegQuality, nullptr); success != android::OK) {
+            &jpegR, jpegQuality,
+            exif.length > 0 ? &exif : NULL); success != android::OK) {
         ALOGW("Encode JPEG/R failed, error code: %d.", success);
         return false;
     }
@@ -415,20 +442,27 @@
 static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
         jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
         jint width, jint height, jint quality, jobject jstream,
-        jbyteArray jstorage) {
+        jbyteArray jstorage, jbyteArray jExif,
+        jintArray jHdrStrides, jintArray jSdrStrides) {
     jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
     jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+    ScopedByteArrayRO exif(env, jExif);
+    ScopedIntArrayRO hdrStrides(env, jHdrStrides);
+    ScopedIntArrayRO sdrStrides(env, jSdrStrides);
+
     SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
     P010Yuv420ToJpegREncoder encoder;
 
     jboolean result = JNI_FALSE;
     if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
-                       width, height, quality)) {
+                       width, height, quality, &exif,
+                       &hdrStrides, &sdrStrides)) {
         result = JNI_TRUE;
     }
 
     env->ReleaseByteArrayElements(inHdr, hdr, 0);
     env->ReleaseByteArrayElements(inSdr, sdr, 0);
+
     delete strm;
     return result;
 }
@@ -437,7 +471,7 @@
 static const JNINativeMethod gYuvImageMethods[] = {
     {   "nativeCompressToJpeg",  "([BIII[I[IILjava/io/OutputStream;[B)Z",
         (void*)YuvImage_compressToJpeg },
-    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B)Z",
+    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z",
         (void*)YuvImage_compressToJpegR }
 };
 
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 8ef7805..a3a3224 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,6 +2,7 @@
 #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
 
 #include <android/data_space.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <ultrahdr/jpegr.h>
 
 extern "C" {
@@ -90,11 +91,15 @@
      *  @param width Width of the Yuv data in terms of pixels.
      *  @param height Height of the Yuv data in terms of pixels.
      *  @param jpegQuality Picture quality in [0, 100].
+     *  @param exif Buffer holds EXIF package.
+     *  @param hdrStrides The number of row bytes in each image plane of the HDR input.
+     *  @param sdrStrides The number of row bytes in each image plane of the SDR input.
      *  @return true if successfully compressed the stream.
      */
     bool encode(JNIEnv* env,
             SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-            int width, int height, int jpegQuality);
+            int width, int height, int jpegQuality, ScopedByteArrayRO* exif,
+            ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides);
 
     /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
      *  used in JPEG/R
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 2ec94c9..f405aba 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -37,6 +37,7 @@
 #include <minikin/LocaleList.h>
 #include <minikin/SystemFonts.h>
 #include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
 
 #include <memory>
 
@@ -459,7 +460,7 @@
     args.setCollectionIndex(ttcIndex);
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
 
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
     if (face == nullptr) {
         return nullptr;
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 2b2e399..ffa915a 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -56,6 +56,7 @@
                                                      int nestLevel) const {
     LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
     for (auto& child : displayList.mChildNodes) {
+        if (!child.getRenderNode()->isRenderable()) continue;
         const RenderProperties& childProperties = child.getNodeProperties();
 
         // immediate children cannot be projected on their parent
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index e5bd5c9..b9dc1c4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -96,6 +96,8 @@
 
     bool hasText() const { return mDisplayList.hasText(); }
 
+    bool hasFill() const { return mDisplayList.hasFill(); }
+
     /**
      * Attempts to reset and reuse this DisplayList.
      *
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 6df34be..64d38b9 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -150,11 +150,11 @@
     }
 
     AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
-            .usage = mUsage,
-            .format = mFormat,
             .width = 1,
             .height = 1,
             .layers = 1,
+            .format = mFormat,
+            .usage = mUsage,
             .rfu0 = 0,
             .rfu1 = 0,
     };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index c3c136f..eab3605 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -123,7 +123,7 @@
 }
 
 void RenderProxy::allocateBuffers() {
-    mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
+    mRenderThread.queue().post([this]() { mContext->allocateBuffers(); });
 }
 
 bool RenderProxy::pause() {
@@ -136,15 +136,16 @@
 
 void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
     mRenderThread.queue().post(
-            [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
+            [=, this]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
 }
 
 void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) {
-    mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); });
+    mRenderThread.queue().post(
+            [=, this]() { mContext->setLightGeometry(lightCenter, lightRadius); });
 }
 
 void RenderProxy::setOpaque(bool opaque) {
-    mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); });
+    mRenderThread.queue().post([=, this]() { mContext->setOpaque(opaque); });
 }
 
 float RenderProxy::setColorMode(ColorMode mode) {
@@ -152,9 +153,9 @@
     // an async call since we already know the return value
     if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
         return mRenderThread.queue().runSync(
-                [=]() -> float { return mContext->setColorMode(mode); });
+                [=, this]() -> float { return mContext->setColorMode(mode); });
     } else {
-        mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+        mRenderThread.queue().post([=, this]() { mContext->setColorMode(mode); });
         return 1.f;
     }
 }
@@ -179,7 +180,7 @@
     // destroyCanvasAndSurface() needs a fence as when it returns the
     // underlying BufferQueue is going to be released from under
     // the render thread.
-    mRenderThread.queue().runSync([=]() { mContext->destroy(); });
+    mRenderThread.queue().runSync([this]() { mContext->destroy(); });
 }
 
 void RenderProxy::destroyFunctor(int functor) {
@@ -300,7 +301,7 @@
 }
 
 void RenderProxy::resetProfileInfo() {
-    mRenderThread.queue().runSync([=]() {
+    mRenderThread.queue().runSync([this]() {
         std::lock_guard lock(mRenderThread.getJankDataMutex());
         mContext->resetFrameStats();
     });
@@ -355,15 +356,15 @@
 }
 
 void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
-    mRenderThread.queue().post([=]() { mContext->addRenderNode(node, placeFront); });
+    mRenderThread.queue().post([=, this]() { mContext->addRenderNode(node, placeFront); });
 }
 
 void RenderProxy::removeRenderNode(RenderNode* node) {
-    mRenderThread.queue().post([=]() { mContext->removeRenderNode(node); });
+    mRenderThread.queue().post([=, this]() { mContext->removeRenderNode(node); });
 }
 
 void RenderProxy::drawRenderNode(RenderNode* node) {
-    mRenderThread.queue().runSync([=]() { mContext->prepareAndDraw(node); });
+    mRenderThread.queue().runSync([=, this]() { mContext->prepareAndDraw(node); });
 }
 
 void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index f76ea06..623ee4e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -150,7 +150,7 @@
         ATRACE_FORMAT("queue mFrameCallbackTask to run after %.2fms",
                       toFloatMillis(runAt - SteadyClock::now()).count());
         queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(),
-                       [=]() { dispatchFrameCallbacks(); });
+                       [this]() { dispatchFrameCallbacks(); });
     }
 }
 
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 1f6edf3..18c5047 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -225,9 +225,9 @@
 TEST(CanvasOp, simpleDrawRect) {
     CanvasOpBuffer buffer;
     EXPECT_EQ(buffer.size(), 0);
-    buffer.push<Op::DrawRect> ({
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty()
+    buffer.push<Op::DrawRect>({
+            .rect = SkRect::MakeEmpty(),
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -242,9 +242,9 @@
     EXPECT_EQ(buffer.size(), 0);
     SkRegion region;
     region.setRect(SkIRect::MakeWH(12, 50));
-    buffer.push<Op::DrawRegion> ({
-        .paint = SkPaint{},
-        .region = region
+    buffer.push<Op::DrawRegion>({
+            .region = region,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -264,9 +264,9 @@
     clip.setRect(SkIRect::MakeWH(100, 100));
     SkRegion region;
     region.setPath(path, clip);
-    buffer.push<Op::DrawRegion> ({
-        .paint = SkPaint{},
-        .region = region
+    buffer.push<Op::DrawRegion>({
+            .region = region,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -279,11 +279,11 @@
 TEST(CanvasOp, simpleDrawRoundRect) {
     CanvasOpBuffer buffer;
     EXPECT_EQ(buffer.size(), 0);
-    buffer.push<Op::DrawRoundRect> ({
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty(),
-        .rx = 10,
-        .ry = 10
+    buffer.push<Op::DrawRoundRect>({
+            .rect = SkRect::MakeEmpty(),
+            .rx = 10,
+            .ry = 10,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -611,9 +611,9 @@
 
     EXPECT_EQ(0, canvas->sumTotalDrawCalls());
     ImmediateModeRasterizer rasterizer{canvas};
-    auto op = CanvasOp<Op::DrawRect> {
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty()
+    auto op = CanvasOp<Op::DrawRect>{
+            .rect = SkRect::MakeEmpty(),
+            .paint = SkPaint{},
     };
     EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>);
     rasterizer.draw(op);
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 8273524..e727ea8 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -331,3 +331,31 @@
     EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
     canvasContext->destroy();
 }
+
+TEST(RenderNode, hasNoFill) {
+    auto rootNode =
+            TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+                Paint paint;
+                paint.setStyle(SkPaint::Style::kStroke_Style);
+                canvas.drawRect(10, 10, 100, 100, paint);
+            });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill());
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
+
+TEST(RenderNode, hasFill) {
+    auto rootNode =
+            TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+                Paint paint;
+                paint.setStyle(SkPaint::kStrokeAndFill_Style);
+                canvas.drawRect(10, 10, 100, 100, paint);
+            });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+    EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill());
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 499afa0..c71c4d2 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -29,6 +29,7 @@
 
 #include "hwui/MinikinSkia.h"
 #include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
 
 using namespace android;
 
@@ -56,7 +57,7 @@
     sk_sp<SkData> skData =
             SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
index db2be20..c70a304 100644
--- a/libs/hwui/tests/unit/UnderlineTest.cpp
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -36,6 +36,7 @@
 #include "hwui/MinikinUtils.h"
 #include "hwui/Paint.h"
 #include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
 
 using namespace android;
 
@@ -66,7 +67,7 @@
     sk_sp<SkData> skData =
             SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font =
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/libs/hwui/utils/TypefaceUtils.cpp
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to libs/hwui/utils/TypefaceUtils.cpp
index 8e55695..a30b925 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/libs/hwui/utils/TypefaceUtils.cpp
@@ -14,7 +14,15 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+#include <utils/TypefaceUtils.h>
 
-import org.robolectric.annotation.GraphicsMode;
+#include "include/ports/SkFontMgr_empty.h"
+
+namespace android {
+
+sk_sp<SkFontMgr> FreeTypeFontMgr() {
+    static sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
+    return mgr;
+}
+
+}  // namespace android
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/libs/hwui/utils/TypefaceUtils.h
similarity index 66%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to libs/hwui/utils/TypefaceUtils.h
index 8e55695..c0adeae 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/libs/hwui/utils/TypefaceUtils.h
@@ -14,7 +14,15 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+#pragma once
 
-import org.robolectric.annotation.GraphicsMode;
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+
+namespace android {
+
+// Return an SkFontMgr which is capable of turning bytes into a SkTypeface using Freetype.
+// There are no other fonts inside this SkFontMgr (e.g. no system fonts).
+sk_sp<SkFontMgr> FreeTypeFontMgr();
+
+}  // namespace android
\ No newline at end of file
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index a9da832..8f5f1f6 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -49,6 +49,18 @@
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "file_patterns": [
+        "[^/]*(LoudnessCodec)[^/]*\\.java"
+      ],
+      "name": "LoudnessCodecApiTest",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        }
+      ]
+    }
   ]
 }
-
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5d211f4..1e32349 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -19,7 +19,6 @@
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.content.Context.DEVICE_ID_DEFAULT;
-
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
 
@@ -51,6 +50,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes.AttributeSystemUsage;
 import android.media.CallbackUtil.ListenerInfo;
 import android.media.audiopolicy.AudioPolicy;
@@ -903,6 +903,7 @@
     @UnsupportedAppUsage
     public AudioManager(Context context) {
         setContext(context);
+        initPlatform();
     }
 
     private Context getContext() {
@@ -916,6 +917,9 @@
     }
 
     private void setContext(Context context) {
+        if (context == null) {
+            return;
+        }
         mOriginalContextDeviceId = context.getDeviceId();
         mApplicationContext = context.getApplicationContext();
         if (mApplicationContext != null) {
@@ -1065,7 +1069,7 @@
      * @see #isVolumeFixed()
      */
     public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
-        if (autoPublicVolumeApiHardening()) {
+        if (applyAutoHardening()) {
             final IAudioService service = getService();
             try {
                 service.adjustVolume(direction, flags);
@@ -1104,7 +1108,7 @@
      */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
             @PublicVolumeFlags int flags) {
-        if (autoPublicVolumeApiHardening()) {
+        if (applyAutoHardening()) {
             final IAudioService service = getService();
             try {
                 service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
@@ -9989,6 +9993,30 @@
         }
     }
 
+    //====================================================================
+    // Flag related utilities
+
+    private boolean mIsAutomotive = false;
+
+    private void initPlatform() {
+        try {
+            final Context context = getContext();
+            if (context != null) {
+                mIsAutomotive = context.getPackageManager()
+                        .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error querying system feature for AUTOMOTIVE", e);
+        }
+    }
+
+    private boolean applyAutoHardening() {
+        if (mIsAutomotive && autoPublicVolumeApiHardening()) {
+            return true;
+        }
+        return false;
+    }
+
     //---------------------------------------------------------
     // Inner classes
     //--------------------
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 61b5fd5..367b38a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2462,6 +2462,8 @@
     public static final int PLATFORM_VOICE = 1;
     /** @hide The platform is a television or a set-top box */
     public static final int PLATFORM_TELEVISION = 2;
+    /** @hide The platform is automotive */
+    public static final int PLATFORM_AUTOMOTIVE = 3;
 
     /**
      * @hide
@@ -2478,6 +2480,9 @@
             return PLATFORM_VOICE;
         } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
             return PLATFORM_TELEVISION;
+        } else if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE)) {
+            return PLATFORM_AUTOMOTIVE;
         } else {
             return PLATFORM_DEFAULT;
         }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 12ddf9b..d14775f 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -37,6 +37,7 @@
 import android.media.ICommunicationDeviceDispatcher;
 import android.media.IDeviceVolumeBehaviorDispatcher;
 import android.media.IDevicesForAttributesCallback;
+import android.media.ILoudnessCodecUpdatesDispatcher;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IPreferredMixerAttributesDispatcher;
@@ -51,6 +52,7 @@
 import android.media.ISpatializerOutputCallback;
 import android.media.IStreamAliasingDispatcher;
 import android.media.IVolumeController;
+import android.media.LoudnessCodecInfo;
 import android.media.PlayerBase;
 import android.media.VolumeInfo;
 import android.media.VolumePolicy;
@@ -728,4 +730,18 @@
     @EnforcePermission("MODIFY_AUDIO_ROUTING")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
     boolean isBluetoothVariableLatencyEnabled();
+
+    void registerLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
+
+    void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
+
+    oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet);
+
+    oneway void stopLoudnessCodecUpdates(int piid);
+
+    oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
+
+    oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
+
+    PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
 }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
similarity index 65%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
index 8e55695..16eaaea 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
@@ -14,7 +14,18 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.media;
 
-import org.robolectric.annotation.GraphicsMode;
+import android.os.PersistableBundle;
+
+/**
+ * Interface which provides updates for the clients about MediaCodec loudness
+ * parameter changes.
+ *
+ * {@hide}
+ */
+oneway interface ILoudnessCodecUpdatesDispatcher {
+
+    void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params);
+
+}
\ No newline at end of file
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java
new file mode 100644
index 0000000..92f3372
--- /dev/null
+++ b/media/java/android/media/LoudnessCodecConfigurator.java
@@ -0,0 +1,436 @@
+/*
+ * 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.media;
+
+import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
+import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class for getting recommended loudness parameter updates for audio decoders, according to the
+ * encoded format and current audio routing. Those updates can be automatically applied to the
+ * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management
+ * parameter updates are defined by the CTA-2075 standard.
+ * <p>A new object should be instantiated for each {@link AudioTrack} with the help
+ * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
+ *
+ * TODO: remove hide once API is final
+ * @hide
+ */
+@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+public class LoudnessCodecConfigurator {
+    private static final String TAG = "LoudnessCodecConfigurator";
+
+    /**
+     * Listener used for receiving asynchronous loudness metadata updates.
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public interface OnLoudnessCodecUpdateListener {
+        /**
+         * Contains the MediaCodec key/values that can be set directly to
+         * configure the loudness of the handle's corresponding decoder (see
+         * {@link MediaCodec#setParameters(Bundle)}).
+         *
+         * @param mediaCodec  the mediaCodec that will receive the new parameters
+         * @param codecValues contains loudness key/value pairs that can be set
+         *                    directly on the mediaCodec. The listener can modify
+         *                    these values with their own edits which will be
+         *                    returned for the mediaCodec configuration
+         * @return a Bundle which contains the original computed codecValues
+         * aggregated with user edits. The platform will configure the associated
+         * MediaCodecs with the returned Bundle params.
+         *
+         * TODO: remove hide once API is final
+         * @hide
+         */
+        @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+        @NonNull
+        default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
+                                             @NonNull Bundle codecValues) {
+            return codecValues;
+        }
+    }
+
+    @NonNull private final LoudnessCodecDispatcher mLcDispatcher;
+
+    private final Object mConfiguratorLock = new Object();
+
+    @GuardedBy("mConfiguratorLock")
+    private AudioTrack mAudioTrack;
+
+    @GuardedBy("mConfiguratorLock")
+    private final Executor mExecutor;
+
+    @GuardedBy("mConfiguratorLock")
+    private final OnLoudnessCodecUpdateListener mListener;
+
+    @GuardedBy("mConfiguratorLock")
+    private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecConfigurator}
+     *
+     * <p>This method should be used when the client does not need to alter the
+     * codec loudness parameters before they are applied to the audio decoders.
+     * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
+     *
+     * @return the {@link LoudnessCodecConfigurator} instance
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public static @NonNull LoudnessCodecConfigurator create() {
+        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
+                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
+    }
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecConfigurator}
+     *
+     * <p>This method should be used when the client wants to alter the codec
+     * loudness parameters before they are applied to the audio decoders.
+     * Otherwise, use {@link #create()}.
+     *
+     * @param executor {@link Executor} to handle the callbacks
+     * @param listener used for receiving updates
+     *
+     * @return the {@link LoudnessCodecConfigurator} instance
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public static @NonNull LoudnessCodecConfigurator create(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnLoudnessCodecUpdateListener listener) {
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
+
+        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
+                executor, listener);
+    }
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecConfigurator}
+     *
+     * <p>This method should be used only in testing
+     *
+     * @param service interface for communicating with AudioService
+     * @param executor {@link Executor} to handle the callbacks
+     * @param listener used for receiving updates
+     *
+     * @return the {@link LoudnessCodecConfigurator} instance
+     *
+     * @hide
+     */
+    public static @NonNull LoudnessCodecConfigurator createForTesting(
+            @NonNull IAudioService service,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnLoudnessCodecUpdateListener listener) {
+        Objects.requireNonNull(service, "IAudioService cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
+
+        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service),
+                executor, listener);
+    }
+
+    /** @hide */
+    private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnLoudnessCodecUpdateListener listener) {
+        mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null");
+        mExecutor = Objects.requireNonNull(executor, "Executor cannot be null");
+        mListener = Objects.requireNonNull(listener,
+                "OnLoudnessCodecUpdateListener cannot be null");
+    }
+
+    /**
+     * Sets the {@link AudioTrack} and starts receiving asynchronous updates for
+     * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)})
+     *
+     * <p>The AudioTrack should be the one that receives audio data from the
+     * added audio decoders and is used to determine the device routing on which
+     * the audio streaming will take place. This will directly influence the
+     * loudness parameters.
+     * <p>After calling this method the framework will compute the initial set of
+     * parameters which will be applied to the registered codecs/returned to the
+     * listener for modification.
+     *
+     * @param audioTrack the track that will receive audio data from the provided
+     *                   audio decoders. In case this is {@code null} this
+     *                   method will have the effect of clearing the existing set
+     *                   {@link AudioTrack} and will stop receiving asynchronous
+     *                   loudness updates
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setAudioTrack(AudioTrack audioTrack) {
+        List<LoudnessCodecInfo> codecInfos;
+        int piid = PLAYER_PIID_INVALID;
+        int oldPiid = PLAYER_PIID_INVALID;
+        synchronized (mConfiguratorLock) {
+            if (mAudioTrack != null && mAudioTrack == audioTrack) {
+                Log.v(TAG, "Loudness configurator already started for piid: "
+                        + mAudioTrack.getPlayerIId());
+                return;
+            }
+
+            codecInfos = getLoudnessCodecInfoList_l();
+            if (mAudioTrack != null) {
+                oldPiid = mAudioTrack.getPlayerIId();
+                mLcDispatcher.removeLoudnessCodecListener(this);
+            }
+            if (audioTrack != null) {
+                piid = audioTrack.getPlayerIId();
+                mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener);
+            }
+
+            mAudioTrack = audioTrack;
+        }
+
+        if (oldPiid != PLAYER_PIID_INVALID) {
+            Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid);
+            mLcDispatcher.stopLoudnessCodecUpdates(oldPiid);
+        }
+        if (piid != PLAYER_PIID_INVALID) {
+            Log.v(TAG, "Loudness configurator starting updates for piid: " + piid);
+            mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos);
+        }
+    }
+
+    /**
+     * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack}
+     * which the client sets
+     * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}).
+     *
+     * <p>This method can be called while asynchronous updates are live.
+     *
+     * <p>No new element will be added if the passed {@code mediaCodec} was
+     * previously added.
+     *
+     * @param mediaCodec the codec to start receiving asynchronous loudness
+     *                   updates
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addMediaCodec(@NonNull MediaCodec mediaCodec) {
+        final MediaCodec mc = Objects.requireNonNull(mediaCodec,
+                "MediaCodec for addMediaCodec cannot be null");
+        int piid = PLAYER_PIID_INVALID;
+        final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
+
+        if (mcInfo != null) {
+            synchronized (mConfiguratorLock) {
+                final AtomicBoolean containsCodec = new AtomicBoolean(false);
+                Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
+                    containsCodec.set(!codecSet.add(mc));
+                    return codecSet;
+                });
+                if (newSet == null) {
+                    newSet = new HashSet<>();
+                    newSet.add(mc);
+                    mMediaCodecs.put(mcInfo, newSet);
+                }
+                if (containsCodec.get()) {
+                    Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec);
+                    return;
+                }
+                if (mAudioTrack != null) {
+                    piid = mAudioTrack.getPlayerIId();
+                }
+            }
+
+            if (piid != PLAYER_PIID_INVALID) {
+                mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo);
+            }
+        }
+    }
+
+    /**
+     * Removes the {@link MediaCodec} from receiving loudness updates.
+     *
+     * <p>This method can be called while asynchronous updates are live.
+     *
+     * <p>No elements will be removed if the passed mediaCodec was not added before.
+     *
+     * @param mediaCodec the element to remove for receiving asynchronous updates
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
+        int piid = PLAYER_PIID_INVALID;
+        LoudnessCodecInfo mcInfo;
+        AtomicBoolean removed = new AtomicBoolean(false);
+
+        mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
+                "MediaCodec for removeMediaCodec cannot be null"));
+
+        if (mcInfo != null) {
+            synchronized (mConfiguratorLock) {
+                if (mAudioTrack != null) {
+                    piid = mAudioTrack.getPlayerIId();
+                }
+                mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
+                    removed.set(mcs.remove(mediaCodec));
+                    if (mcs.isEmpty()) {
+                        // remove the entry
+                        return null;
+                    }
+                    return mcs;
+                });
+            }
+
+            if (piid != PLAYER_PIID_INVALID && removed.get()) {
+                mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
+            }
+        }
+    }
+
+    /**
+     * Gets synchronous loudness updates when no listener is required. The provided
+     * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}.
+     *
+     * @param audioTrack track that receives audio data from the passed
+     *                   {@link MediaCodec}
+     * @param mediaCodec codec that decodes loudness annotated data for the passed
+     *                   {@link AudioTrack}
+     *
+     * @return the {@link Bundle} containing the current loudness parameters. Caller is
+     * responsible to update the {@link MediaCodec}
+     *
+     * TODO: remove hide once API is final
+     * @hide
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    @NonNull
+    public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack,
+            @NonNull MediaCodec mediaCodec) {
+        Objects.requireNonNull(audioTrack, "Passed audio track cannot be null");
+
+        LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
+        if (codecInfo == null) {
+            return new Bundle();
+        }
+
+        return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo);
+    }
+
+    /** @hide */
+    /*package*/ int getAssignedTrackPiid() {
+        int piid = PLAYER_PIID_INVALID;
+
+        synchronized (mConfiguratorLock) {
+            if (mAudioTrack == null) {
+                return piid;
+            }
+            piid = mAudioTrack.getPlayerIId();
+        }
+
+        return piid;
+    }
+
+    /** @hide */
+    /*package*/ List<MediaCodec> getRegisteredMediaCodecList() {
+        synchronized (mConfiguratorLock) {
+            return mMediaCodecs.values().stream().flatMap(Collection::stream).toList();
+        }
+    }
+
+    @GuardedBy("mConfiguratorLock")
+    private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() {
+        return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map(
+                LoudnessCodecConfigurator::getCodecInfo)).toList();
+    }
+
+    @Nullable
+    private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) {
+        LoudnessCodecInfo lci = new LoudnessCodecInfo();
+        final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
+        if (codecInfo.isEncoder()) {
+            // loudness info only for decoders
+            Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation");
+            return null;
+        }
+
+        final MediaFormat inputFormat = mediaCodec.getInputFormat();
+        final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
+        if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
+            // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of
+            // these two keys
+            int aacProfile = -1;
+            int profile = -1;
+            try {
+                aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
+            } catch (NullPointerException e) {
+                // does not contain KEY_AAC_PROFILE. do nothing
+            }
+            try {
+                profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
+            } catch (NullPointerException e) {
+                // does not contain KEY_PROFILE. do nothing
+            }
+            if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
+                    || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
+                lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
+            } else {
+                lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
+            }
+        } else {
+            Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
+            return null;
+        }
+
+        final MediaFormat outputFormat = mediaCodec.getOutputFormat();
+        lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
+                < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+
+        lci.mediaCodecHashCode = mediaCodec.hashCode();
+
+        return lci;
+    }
+}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
new file mode 100644
index 0000000..5237cae
--- /dev/null
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -0,0 +1,251 @@
+/*
+ * 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.media;
+
+import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
+import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
+import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
+
+import android.annotation.CallbackExecutor;
+import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Class used to handle the loudness related communication with the audio service.
+ *
+ * @hide
+ */
+public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
+    private static final String TAG = "LoudnessCodecDispatcher";
+
+    private static final boolean DEBUG = false;
+
+    private static final class LoudnessCodecUpdatesDispatcherStub
+            extends ILoudnessCodecUpdatesDispatcher.Stub {
+        private static LoudnessCodecUpdatesDispatcherStub sLoudnessCodecStub;
+
+        private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener>
+                mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>();
+
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>
+                mConfiguratorListener = new HashMap<>();
+
+        public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() {
+            if (sLoudnessCodecStub == null) {
+                sLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub();
+            }
+            return sLoudnessCodecStub;
+        }
+
+        private LoudnessCodecUpdatesDispatcherStub() {}
+
+        @Override
+        public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
+            mLoudnessListenerMgr.callListeners(listener -> {
+                synchronized (mLock) {
+                    mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
+                        // send the appropriate bundle for the user to update
+                        if (lcConfig.getAssignedTrackPiid() == piid) {
+                            final List<MediaCodec> mediaCodecs =
+                                    lcConfig.getRegisteredMediaCodecList();
+                            for (MediaCodec mediaCodec : mediaCodecs) {
+                                final String infoKey = Integer.toString(mediaCodec.hashCode());
+                                if (params.containsKey(infoKey)) {
+                                    Bundle bundle = new Bundle(
+                                            params.getPersistableBundle(infoKey));
+                                    if (DEBUG) {
+                                        Log.d(TAG,
+                                                "Received for piid " + piid + " bundle: " + bundle);
+                                    }
+                                    bundle =
+                                            LoudnessCodecUpdatesDispatcherStub.filterLoudnessParams(
+                                                    l.onLoudnessCodecUpdate(mediaCodec, bundle));
+                                    if (DEBUG) {
+                                        Log.d(TAG, "User changed for piid " + piid
+                                                + " to filtered bundle: " + bundle);
+                                    }
+
+                                    if (!bundle.isDefinitelyEmpty()) {
+                                        mediaCodec.setParameters(bundle);
+                                    }
+                                }
+                            }
+                        }
+                        return lcConfig;
+                    });
+                }
+            });
+        }
+
+        private static Bundle filterLoudnessParams(Bundle bundle) {
+            Bundle filteredBundle = new Bundle();
+
+            if (bundle.containsKey(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)) {
+                filteredBundle.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                        bundle.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+            }
+            if (bundle.containsKey(KEY_AAC_DRC_HEAVY_COMPRESSION)) {
+                filteredBundle.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION,
+                        bundle.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+            }
+            if (bundle.containsKey(KEY_AAC_DRC_EFFECT_TYPE)) {
+                filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE,
+                        bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+            }
+
+            return filteredBundle;
+        }
+
+        void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher,
+                @NonNull LoudnessCodecConfigurator configurator,
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnLoudnessCodecUpdateListener listener) {
+            Objects.requireNonNull(configurator);
+            Objects.requireNonNull(executor);
+            Objects.requireNonNull(listener);
+
+            mLoudnessListenerMgr.addListener(
+                    executor, listener, "addLoudnessCodecListener",
+                    () -> dispatcher);
+            synchronized (mLock) {
+                mConfiguratorListener.put(listener, configurator);
+            }
+        }
+
+        void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+            Objects.requireNonNull(configurator);
+
+            OnLoudnessCodecUpdateListener listenerToRemove = null;
+            synchronized (mLock) {
+                Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator =
+                        mConfiguratorListener.entrySet().iterator();
+                while (iterator.hasNext()) {
+                    Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e =
+                            iterator.next();
+                    if (e.getValue() == configurator) {
+                        final OnLoudnessCodecUpdateListener listener = e.getKey();
+                        iterator.remove();
+                        listenerToRemove = listener;
+                        break;
+                    }
+                }
+            }
+            if (listenerToRemove != null) {
+                mLoudnessListenerMgr.removeListener(listenerToRemove,
+                        "removeLoudnessCodecListener");
+            }
+        }
+    }
+
+    @NonNull private final IAudioService mAudioService;
+
+    /** @hide */
+    public LoudnessCodecDispatcher(@NonNull IAudioService audioService) {
+        mAudioService = Objects.requireNonNull(audioService);
+    }
+
+    @Override
+    public void register(boolean register) {
+        try {
+            if (register) {
+                mAudioService.registerLoudnessCodecUpdatesDispatcher(
+                        LoudnessCodecUpdatesDispatcherStub.getInstance());
+            } else {
+                mAudioService.unregisterLoudnessCodecUpdatesDispatcher(
+                        LoudnessCodecUpdatesDispatcherStub.getInstance());
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator,
+                                         @NonNull @CallbackExecutor Executor executor,
+                                         @NonNull OnLoudnessCodecUpdateListener listener) {
+        LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this,
+                configurator, executor, listener);
+    }
+
+    /** @hide */
+    public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+        LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator);
+    }
+
+    /** @hide */
+    public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+        try {
+            mAudioService.startLoudnessCodecUpdates(piid, codecInfoList);
+        }  catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void stopLoudnessCodecUpdates(int piid) {
+        try {
+            mAudioService.stopLoudnessCodecUpdates(piid);
+        }  catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void addLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+        try {
+            mAudioService.addLoudnessCodecInfo(piid, mcInfo);
+        }  catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void removeLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+        try {
+            mAudioService.removeLoudnessCodecInfo(piid, mcInfo);
+        }  catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public Bundle getLoudnessCodecParams(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+        Bundle loudnessParams = null;
+        try {
+            loudnessParams = new Bundle(mAudioService.getLoudnessParams(piid, mcInfo));
+        }  catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return loudnessParams;
+    }
+}
diff --git a/media/java/android/media/LoudnessCodecInfo.aidl b/media/java/android/media/LoudnessCodecInfo.aidl
new file mode 100644
index 0000000..fd69517
--- /dev/null
+++ b/media/java/android/media/LoudnessCodecInfo.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * Loudness information for a {@link MediaCodec} object which specifies the
+ * input attributes used for measuring the parameters required to perform
+ * loudness alignment as specified by the CTA2075 standard.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals = true)
+parcelable LoudnessCodecInfo {
+    /** Supported codec metadata types for loudness updates. */
+    @Backing(type="int")
+    enum CodecMetadataType {
+        CODEC_METADATA_TYPE_INVALID = 0,
+        CODEC_METADATA_TYPE_MPEG_4 = 1,
+        CODEC_METADATA_TYPE_MPEG_D = 2,
+        CODEC_METADATA_TYPE_AC_3 = 3,
+        CODEC_METADATA_TYPE_AC_4 = 4,
+        CODEC_METADATA_TYPE_DTS_HD = 5,
+        CODEC_METADATA_TYPE_DTS_UHD = 6
+    }
+
+    int mediaCodecHashCode;
+    CodecMetadataType metadataType;
+    boolean isDownmixing;
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index 5509782..f522974 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -148,6 +148,7 @@
                 createKey("headTrackerEnabled", String.class); // spatializer
 
         public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
+        public static final Key<Integer> OLD_INDEX = createKey("oldIndex", Integer.class); // volume
         public static final Key<Integer> INPUT_PORT_COUNT =
                 createKey("inputPortCount", Integer.class); // MIDI
         // Either "true" or "false"
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 6031ef7..94fce79 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -122,11 +122,6 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
-
-    // Workaround Clang LTO crash.
-    lto: {
-        never: true,
-    },
 }
 
 cc_library_shared {
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 8b5b726..cf5059c 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -44,9 +44,4 @@
         "-Wunreachable-code",
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
-
-    // Workaround Clang LTO crash.
-    lto: {
-        never: true,
-    },
 }
diff --git a/media/tests/LoudnessCodecApiTest/Android.bp b/media/tests/LoudnessCodecApiTest/Android.bp
new file mode 100644
index 0000000..5ca0fc9
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/Android.bp
@@ -0,0 +1,27 @@
+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: "LoudnessCodecApiTest",
+    srcs: ["**/*.java"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "junit",
+        "junit-params",
+        "mockito-target-minus-junit4",
+        "flag-junit",
+        "hamcrest-library",
+        "platform-test-annotations",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    resource_dirs: ["res"],
+    test_suites: ["device-tests"],
+}
diff --git a/media/tests/LoudnessCodecApiTest/AndroidManifest.xml b/media/tests/LoudnessCodecApiTest/AndroidManifest.xml
new file mode 100644
index 0000000..91a671f
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.loudnesscodecapitest">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.loudnesscodecapitest"
+            android:label="AudioManager loudness codec integration tests InstrumentationRunner">
+    </instrumentation>
+</manifest>
diff --git a/media/tests/LoudnessCodecApiTest/AndroidTest.xml b/media/tests/LoudnessCodecApiTest/AndroidTest.xml
new file mode 100644
index 0000000..0099d98
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 Media Framework Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="LoudnessCodecApiTest.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="LoudnessCodecApiTest" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.loudnesscodecapitest" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/media/tests/LoudnessCodecApiTest/res/layout/loudnesscodecapitest.xml b/media/tests/LoudnessCodecApiTest/res/layout/loudnesscodecapitest.xml
new file mode 100644
index 0000000..17fdba6
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/res/layout/loudnesscodecapitest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+</LinearLayout>
diff --git a/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a b/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
new file mode 100644
index 0000000..acba4b3
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
Binary files differ
diff --git a/media/tests/LoudnessCodecApiTest/res/values/strings.xml b/media/tests/LoudnessCodecApiTest/res/values/strings.xml
new file mode 100644
index 0000000..0c4227c
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- name of the app [CHAR LIMIT=25]-->
+    <string name="app_name">Loudness Codec API Tests</string>
+</resources>
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
new file mode 100644
index 0000000..65a9799
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.loudnesscodecapitest;
+
+import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.media.IAudioService;
+import android.media.LoudnessCodecConfigurator;
+import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+
+/**
+ * Unit tests for {@link LoudnessCodecConfigurator} checking the internal interactions with a mocked
+ * {@link IAudioService} without any real IPC interactions.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LoudnessCodecConfiguratorTest {
+    private static final String TAG = "LoudnessCodecConfiguratorTest";
+
+    private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
+    private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048;
+    private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
+    private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Mock
+    private IAudioService mAudioService;
+
+    private LoudnessCodecConfigurator mLcc;
+
+    @Before
+    public void setUp() {
+        mLcc = LoudnessCodecConfigurator.createForTesting(mAudioService,
+                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setAudioTrack_callsAudioServiceStart() throws Exception {
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(track);
+
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                anyList());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
+        when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle());
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.getLoudnessCodecParams(track, createAndConfigureMediaCodec());
+
+        verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setAudioTrack_addsAudioServicePiidCodecs() throws Exception {
+        final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.setAudioTrack(track);
+
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setAudioTrackTwice_ignoresSecondCall() throws Exception {
+        final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.setAudioTrack(track);
+        mLcc.setAudioTrack(track);
+
+        verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                anyList());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setTrackNull_stopCodecUpdates() throws Exception {
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(track);
+
+        mLcc.setAudioTrack(null);  // stops updates
+        verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addMediaCodecTwice_ignoresSecondCall() throws Exception {
+        final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
+        final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.setAudioTrack(track);
+
+        verify(mAudioService, times(1)).startLoudnessCodecUpdates(
+                eq(track.getPlayerIId()), argument.capture());
+        assertEquals(argument.getValue().size(), 1);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
+        final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
+
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(track);
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                argument.capture());
+        assertEquals(argument.getValue().size(), 1);
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(null);
+        verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception {
+        final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.setAudioTrack(track);
+        mLcc.removeMediaCodec(mediaCodec);
+
+        verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(track);
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception {
+        final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        mLcc.addMediaCodec(mediaCodec);
+        mLcc.setAudioTrack(track);
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+
+        mLcc.removeMediaCodec(mediaCodec);
+        verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeWrongMediaCodecAfterSetTrack_noAudioServiceRemoveCall() throws Exception {
+        final AudioTrack track = createAudioTrack();
+
+        mLcc.addMediaCodec(createAndConfigureMediaCodec());
+        mLcc.setAudioTrack(track);
+        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+
+        mLcc.removeMediaCodec(createAndConfigureMediaCodec());
+        verify(mAudioService, times(0)).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+    }
+
+    private static AudioTrack createAudioTrack() {
+        return new AudioTrack.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder().build())
+                .setBufferSizeInBytes(TEST_AUDIO_TRACK_BUFFER_SIZE)
+                .setAudioFormat(new AudioFormat.Builder()
+                        .setChannelMask(TEST_AUDIO_TRACK_CHANNELS)
+                        .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE).build())
+                .build();
+    }
+
+    private MediaCodec createAndConfigureMediaCodec() throws Exception {
+        AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
+                .getResources()
+                .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
+
+        MediaExtractor extractor;
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
+        final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
+
+        Log.v(TAG, "configuring with " + format);
+        mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        return mediaCodec;
+    }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index fd785a4..b795560 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -55,6 +55,8 @@
 ?application/vnd.android.haptics.vibration+xml ahv
 ?application/vnd.android.ota ota
 ?application/vnd.apple.mpegurl m3u8
+?application/vnd.apple.pkpass pkpass
+?application/vnd.apple.pkpasses pkpasses
 ?application/vnd.ms-pki.stl stl
 ?application/vnd.ms-powerpoint pot
 ?application/vnd.ms-wpl wpl
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9f2a9ac..fea6c5f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -336,13 +336,6 @@
     APerformanceHint_closeSession; # introduced=Tiramisu
     APerformanceHint_setThreads; # introduced=UpsideDownCake
     APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
-    APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
-    AWorkDuration_create; # introduced=VanillaIceCream
-    AWorkDuration_release; # introduced=VanillaIceCream
-    AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
-    AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream
-    AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream
-    AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c4c8128..c25df6e 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,14 +18,12 @@
 
 #include <aidl/android/hardware/power/SessionHint.h>
 #include <aidl/android/hardware/power/SessionMode.h>
-#include <android/WorkDuration.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
-#include <inttypes.h>
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
@@ -77,13 +75,10 @@
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
     int setPreferPowerEfficiency(bool enabled);
-    int reportActualWorkDuration(AWorkDuration* workDuration);
 
 private:
     friend struct APerformanceHintManager;
 
-    int reportActualWorkDurationInternal(WorkDuration* workDuration);
-
     sp<IHintManager> mHintManager;
     sp<IHintSession> mHintSession;
     // HAL preferred update rate
@@ -97,7 +92,8 @@
     // Last hint reported from sendHint indexed by hint value
     std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
-    std::vector<WorkDuration> mActualWorkDurations;
+    std::vector<int64_t> mActualDurationsNanos;
+    std::vector<int64_t> mTimestampsNanos;
 };
 
 static IHintManager* gIHintManagerForTesting = nullptr;
@@ -199,7 +195,8 @@
      * Most of the workload is target_duration dependent, so now clear the cached samples
      * as they are most likely obsolete.
      */
-    mActualWorkDurations.clear();
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
     mFirstTargetMetTimestamp = 0;
     mLastTargetMetTimestamp = 0;
     return 0;
@@ -210,10 +207,43 @@
         ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
         return EINVAL;
     }
+    int64_t now = elapsedRealtimeNano();
+    mActualDurationsNanos.push_back(actualDurationNanos);
+    mTimestampsNanos.push_back(now);
 
-    WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
+    if (actualDurationNanos >= mTargetDurationNanos) {
+        // Reset timestamps if we are equal or over the target.
+        mFirstTargetMetTimestamp = 0;
+    } else {
+        // Set mFirstTargetMetTimestamp for first time meeting target.
+        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+            mFirstTargetMetTimestamp = now;
+        }
+        /**
+         * Rate limit the change if the update is over mPreferredRateNanos since first
+         * meeting target and less than mPreferredRateNanos since last meeting target.
+         */
+        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+            return 0;
+        }
+        mLastTargetMetTimestamp = now;
+    }
 
-    return reportActualWorkDurationInternal(&workDuration);
+    binder::Status ret =
+            mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        mFirstTargetMetTimestamp = 0;
+        mLastTargetMetTimestamp = 0;
+        return EPIPE;
+    }
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+
+    return 0;
 }
 
 int APerformanceHintSession::sendHint(SessionHint hint) {
@@ -292,67 +322,6 @@
     return OK;
 }
 
-int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
-    WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
-    if (workDuration->workPeriodStartTimestampNanos <= 0) {
-        ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualTotalDurationNanos <= 0) {
-        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualCpuDurationNanos <= 0) {
-        ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualGpuDurationNanos < 0) {
-        ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
-        return EINVAL;
-    }
-
-    return reportActualWorkDurationInternal(workDuration);
-}
-
-int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) {
-    int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
-    int64_t now = uptimeNanos();
-    workDuration->timestampNanos = now;
-    mActualWorkDurations.push_back(std::move(*workDuration));
-
-    if (actualTotalDurationNanos >= mTargetDurationNanos) {
-        // Reset timestamps if we are equal or over the target.
-        mFirstTargetMetTimestamp = 0;
-    } else {
-        // Set mFirstTargetMetTimestamp for first time meeting target.
-        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
-            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
-            mFirstTargetMetTimestamp = now;
-        }
-        /**
-         * Rate limit the change if the update is over mPreferredRateNanos since first
-         * meeting target and less than mPreferredRateNanos since last meeting target.
-         */
-        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
-            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
-            return 0;
-        }
-        mLastTargetMetTimestamp = now;
-    }
-
-    binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations);
-    if (!ret.isOk()) {
-        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
-              ret.exceptionMessage().c_str());
-        mFirstTargetMetTimestamp = 0;
-        mLastTargetMetTimestamp = 0;
-        return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
-    }
-    mActualWorkDurations.clear();
-
-    return 0;
-}
-
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
     return APerformanceHintManager::getInstance();
@@ -407,64 +376,6 @@
     return session->setPreferPowerEfficiency(enabled);
 }
 
-int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
-                                               AWorkDuration* workDuration) {
-    if (session == nullptr || workDuration == nullptr) {
-        ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
-        return EINVAL;
-    }
-    return session->reportActualWorkDuration(workDuration);
-}
-
-AWorkDuration* AWorkDuration_create() {
-    WorkDuration* workDuration = new WorkDuration();
-    return static_cast<AWorkDuration*>(workDuration);
-}
-
-void AWorkDuration_release(AWorkDuration* aWorkDuration) {
-    if (aWorkDuration == nullptr) {
-        ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
-    }
-    delete aWorkDuration;
-}
-
-void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
-                                                    int64_t workPeriodStartTimestampNanos) {
-    if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
-    }
-    static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
-            workPeriodStartTimestampNanos;
-}
-
-void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
-                                               int64_t actualTotalDurationNanos) {
-    if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
-    }
-    static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
-}
-
-void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
-                                             int64_t actualCpuDurationNanos) {
-    if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
-    }
-    static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
-}
-
-void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
-                                             int64_t actualGpuDurationNanos) {
-    if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
-    }
-    static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
-}
-
 void APerformanceHint_setIHintManagerForTesting(void* iManager) {
     delete gHintManagerForTesting;
     gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 4553b49..22d33b1 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -16,7 +16,6 @@
 
 #define LOG_TAG "PerformanceHintNativeTest"
 
-#include <android/WorkDuration.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -61,8 +60,6 @@
     MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-    MOCK_METHOD(Status, reportActualWorkDuration2,
-                (const ::std::vector<android::os::WorkDuration>& workDurations), (override));
 };
 
 class PerformanceHintTest : public Test {
@@ -123,7 +120,6 @@
     std::vector<int64_t> actualDurations;
     actualDurations.push_back(20);
     EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
-    EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1));
     result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
     EXPECT_EQ(0, result);
 
@@ -242,125 +238,4 @@
     APerformanceHintSession* session =
             APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
     ASSERT_TRUE(session);
-}
-
-MATCHER_P(WorkDurationEq, expected, "") {
-    if (arg.size() != expected.size()) {
-        *result_listener << "WorkDuration vectors are different sizes. Expected: "
-                         << expected.size() << ", Actual: " << arg.size();
-        return false;
-    }
-    for (int i = 0; i < expected.size(); ++i) {
-        android::os::WorkDuration expectedWorkDuration = expected[i];
-        android::os::WorkDuration actualWorkDuration = arg[i];
-        if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) {
-            *result_listener << "WorkDuration at [" << i << "] is different: "
-                             << "Expected: " << expectedWorkDuration
-                             << ", Actual: " << actualWorkDuration;
-            return false;
-        }
-    }
-    return true;
-}
-
-TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) {
-    APerformanceHintManager* manager = createManager();
-
-    std::vector<int32_t> tids;
-    tids.push_back(1);
-    tids.push_back(2);
-    int64_t targetDuration = 56789L;
-
-    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
-    sp<IHintSession> session_sp(iSession);
-
-    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
-            .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
-
-    APerformanceHintSession* session =
-            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
-    ASSERT_TRUE(session);
-
-    int64_t targetDurationNanos = 10;
-    EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
-    int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
-    EXPECT_EQ(0, result);
-
-    usleep(2); // Sleep for longer than preferredUpdateRateNanos.
-    {
-        std::vector<android::os::WorkDuration> actualWorkDurations;
-        android::os::WorkDuration workDuration(1, 20, 13, 8);
-        actualWorkDurations.push_back(workDuration);
-
-        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
-                .Times(Exactly(1));
-        result = APerformanceHint_reportActualWorkDuration2(session,
-                                                            static_cast<AWorkDuration*>(
-                                                                    &workDuration));
-        EXPECT_EQ(0, result);
-    }
-
-    {
-        std::vector<android::os::WorkDuration> actualWorkDurations;
-        android::os::WorkDuration workDuration(-1, 20, 13, 8);
-        actualWorkDurations.push_back(workDuration);
-
-        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
-                .Times(Exactly(1));
-        result = APerformanceHint_reportActualWorkDuration2(session,
-                                                            static_cast<AWorkDuration*>(
-                                                                    &workDuration));
-        EXPECT_EQ(22, result);
-    }
-    {
-        std::vector<android::os::WorkDuration> actualWorkDurations;
-        android::os::WorkDuration workDuration(1, -20, 13, 8);
-        actualWorkDurations.push_back(workDuration);
-
-        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
-                .Times(Exactly(1));
-        result = APerformanceHint_reportActualWorkDuration2(session,
-                                                            static_cast<AWorkDuration*>(
-                                                                    &workDuration));
-        EXPECT_EQ(22, result);
-    }
-    {
-        std::vector<android::os::WorkDuration> actualWorkDurations;
-        android::os::WorkDuration workDuration(1, 20, -13, 8);
-        actualWorkDurations.push_back(workDuration);
-
-        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
-                .Times(Exactly(1));
-        result = APerformanceHint_reportActualWorkDuration2(session,
-                                                            static_cast<AWorkDuration*>(
-                                                                    &workDuration));
-        EXPECT_EQ(EINVAL, result);
-    }
-    {
-        std::vector<android::os::WorkDuration> actualWorkDurations;
-        android::os::WorkDuration workDuration(1, 20, 13, -8);
-        actualWorkDurations.push_back(workDuration);
-
-        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
-                .Times(Exactly(1));
-        result = APerformanceHint_reportActualWorkDuration2(session,
-                                                            static_cast<AWorkDuration*>(
-                                                                    &workDuration));
-        EXPECT_EQ(EINVAL, result);
-    }
-
-    EXPECT_CALL(*iSession, close()).Times(Exactly(1));
-    APerformanceHint_closeSession(session);
-}
-
-TEST_F(PerformanceHintTest, TestAWorkDuration) {
-    AWorkDuration* aWorkDuration = AWorkDuration_create();
-    ASSERT_NE(aWorkDuration, nullptr);
-
-    AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1);
-    AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20);
-    AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13);
-    AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8);
-    AWorkDuration_release(aWorkDuration);
-}
+}
\ No newline at end of file
diff --git a/nfc/Android.bp b/nfc/Android.bp
new file mode 100644
index 0000000..bf9f47c
--- /dev/null
+++ b/nfc/Android.bp
@@ -0,0 +1,51 @@
+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"],
+}
+
+filegroup {
+    name: "framework-nfc-non-updatable-sources",
+    path: "java",
+    srcs: [],
+}
+
+filegroup {
+    name: "framework-nfc-updatable-sources",
+    path: "java",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    exclude_srcs: [
+        ":framework-nfc-non-updatable-sources",
+    ],
+}
+
+java_sdk_library {
+    name: "framework-nfc",
+    libs: [
+        "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+    ],
+    srcs: [
+        ":framework-nfc-updatable-sources",
+    ],
+    defaults: ["framework-non-updatable-unbundled-defaults"],
+    permitted_packages: [
+        "android.nfc",
+        "com.android.nfc",
+    ],
+    hidden_api_packages: [
+        "com.android.nfc",
+    ],
+    aidl: {
+        include_dirs: [
+	    // TODO (b/303286040): Remove these when we change to |framework-module-defaults|
+            "frameworks/base/nfc/java",
+            "frameworks/base/core/java",
+        ],
+    },
+}
diff --git a/nfc/OWNERS b/nfc/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc/TEST_MAPPING b/nfc/TEST_MAPPING
new file mode 100644
index 0000000..5b5ea37
--- /dev/null
+++ b/nfc/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "NfcManagerTests"
+    },
+    {
+      "name": "CtsNfcTestCases"
+    }
+  ]
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/module-lib-removed.txt b/nfc/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/test-current.txt b/nfc/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nfc/api/test-removed.txt b/nfc/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/nfc/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/nfc/java/android/nfc/Placeholder.java
similarity index 76%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to nfc/java/android/nfc/Placeholder.java
index 8e55695..3509644 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/nfc/java/android/nfc/Placeholder.java
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.nfc;
 
-import org.robolectric.annotation.GraphicsMode;
+/**
+ * Placeholder class so new framework-nfc module isn't empty, will be removed once module is
+ * populated.
+ *
+ * @hide
+ *
+ */
+public class Placeholder {
+}
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
new file mode 100644
index 0000000..daa0211
--- /dev/null
+++ b/packages/CrashRecovery/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
new file mode 100644
index 0000000..b2af315
--- /dev/null
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -0,0 +1,9 @@
+filegroup {
+    name: "framework-crashrecovery-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base:__subpackages__"],
+}
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
similarity index 100%
rename from core/java/android/service/watchdog/ExplicitHealthCheckService.java
rename to packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
diff --git a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
similarity index 96%
rename from core/java/android/service/watchdog/IExplicitHealthCheckService.aidl
rename to packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
index 78c0328..9096509 100644
--- a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
@@ -21,6 +21,7 @@
 /**
  * @hide
  */
+@PermissionManuallyEnforced
 oneway interface IExplicitHealthCheckService
 {
     void setCallback(in @nullable RemoteCallback callback);
diff --git a/core/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
similarity index 100%
rename from core/java/android/service/watchdog/OWNERS
rename to packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
diff --git a/core/java/android/service/watchdog/PackageConfig.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
similarity index 100%
rename from core/java/android/service/watchdog/PackageConfig.aidl
rename to packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
new file mode 100644
index 0000000..27ddff9
--- /dev/null
+++ b/packages/CrashRecovery/services/Android.bp
@@ -0,0 +1,9 @@
+filegroup {
+    name: "services-crashrecovery-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base:__subpackages__"],
+}
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
similarity index 100%
rename from services/core/java/com/android/server/ExplicitHealthCheckController.java
rename to packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
similarity index 100%
rename from services/core/java/com/android/server/PackageWatchdog.java
rename to packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
diff --git a/services/core/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
similarity index 100%
rename from services/core/java/com/android/server/RescueParty.java
rename to packages/CrashRecovery/services/java/com/android/server/RescueParty.java
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
similarity index 100%
rename from services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
rename to packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
similarity index 100%
rename from services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
rename to packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 98ad22c..42f1207 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -19,35 +19,46 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.credentials.ui.RequestInfo
+import android.util.Log
+import com.android.credentialmanager.ktx.appLabel
+import com.android.credentialmanager.ktx.cancelUiRequest
 import com.android.credentialmanager.ktx.requestInfo
 import com.android.credentialmanager.mapper.toGet
-import com.android.credentialmanager.mapper.toRequestCancel
-import com.android.credentialmanager.mapper.toRequestClose
 import com.android.credentialmanager.model.Request
 
 fun Intent.parse(
     packageManager: PackageManager,
-    previousIntent: Intent? = null,
 ): Request {
-    this.toRequestClose(previousIntent)?.let { closeRequest ->
-        return closeRequest
-    }
-
-    this.toRequestCancel(packageManager)?.let { cancelRequest ->
-        return cancelRequest
-    }
-
-    return when (requestInfo?.type) {
-        RequestInfo.TYPE_CREATE -> {
-            Request.Create
-        }
-
-        RequestInfo.TYPE_GET -> {
-            this.toGet()
-        }
-
-        else -> {
-            throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}")
-        }
-    }
+    return parseCancelUiRequest(packageManager)
+        ?: parseRequestInfo()
 }
+
+fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? =
+    this.cancelUiRequest?.let { cancelUiRequest ->
+        val showCancel = cancelUiRequest.shouldShowCancellationUi().apply {
+            Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this")
+        }
+        if (showCancel) {
+            val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName)
+            if (appLabel == null) {
+                Log.d(TAG, "Received UI cancel request with an invalid package name.")
+                null
+            } else {
+                Request.Cancel(appName = appLabel, token = cancelUiRequest.token)
+            }
+        } else {
+            Request.Close(cancelUiRequest.token)
+        }
+    }
+
+fun Intent.parseRequestInfo(): Request =
+    requestInfo.let{ info ->
+        when (info?.type) {
+            RequestInfo.TYPE_CREATE -> Request.Create(info.token)
+            RequestInfo.TYPE_GET -> toGet()
+            else -> {
+                throw IllegalStateException("Unrecognized request type: ${info?.type}")
+            }
+        }
+    }
+
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
new file mode 100644
index 0000000..49387cf
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.client
+
+import android.content.Intent
+import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.UserSelectionDialogResult
+import com.android.credentialmanager.model.Request
+import kotlinx.coroutines.flow.StateFlow
+
+interface CredentialManagerClient {
+    /** The UI should monitor the request update. */
+    val requests: StateFlow<Request?>
+
+    /** The UI got a new intent; update the request state. */
+    fun updateRequest(intent: Intent)
+
+    /** Sends an error encountered during the UI. */
+    fun sendError(
+        @BaseDialogResult.ResultCode resultCode: Int,
+        errorMessage: String? = null,
+    )
+
+    /**
+     * Sends a response to the system service. The response
+     * contains information about the user's choice from the selector
+     * UI and the result of the provider operation launched with
+     * that selection.
+     *
+     * If the user choice was a normal entry, then the UI can finish
+     * the activity immediately. Otherwise if it was an authentication
+     * (locked) entry, then the UI will need to stay up and wait for
+     * a new intent from the system containing the new data for
+     * display.
+     *
+     * Note that if the provider operation returns RESULT_CANCELED,
+     * then the selector should not send that result back, and instead
+     * re-display the options to allow a user to have another choice.
+     *
+     * @throws [IllegalStateException] if [requests] is not [Request.Get].
+     */
+    fun sendResult(result: UserSelectionDialogResult)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
new file mode 100644
index 0000000..83183b5
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.client.impl
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.UserSelectionDialogResult
+import android.os.Bundle
+import android.util.Log
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.parse
+import com.android.credentialmanager.client.CredentialManagerClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+class CredentialManagerClientImpl @Inject constructor(
+        private val packageManager: PackageManager,
+) : CredentialManagerClient {
+
+    private val _requests = MutableStateFlow<Request?>(null)
+    override val requests: StateFlow<Request?> = _requests
+
+
+    override fun updateRequest(intent: Intent) {
+        val request = intent.parse(
+            packageManager = packageManager,
+        )
+        Log.d(TAG, "Request parsed: $request, client instance: $this")
+        if (request is Request.Cancel || request is Request.Close) {
+            if (request.token != null && request.token != _requests.value?.token) {
+                Log.w(TAG, "drop terminate request for previous session.")
+                return
+            }
+        }
+        _requests.value = request
+    }
+
+    override fun sendError(resultCode: Int, errorMessage: String?) {
+        TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+    }
+
+    override fun sendResult(result: UserSelectionDialogResult) {
+        val currentRequest = requests.value
+        check(currentRequest is Request.Get) { "current request is not get." }
+        currentRequest.resultReceiver?.let { receiver ->
+            val resultDataBundle = Bundle()
+            UserSelectionDialogResult.addToBundle(result, resultDataBundle)
+            receiver.send(
+                BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+                resultDataBundle
+            )
+        }
+    }
+}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4533db6..3abdb6f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -37,17 +37,17 @@
         RequestInfo::class.java
     )
 
-val Intent.getCredentialProviderDataList: List<ProviderData>
+val Intent.getCredentialProviderDataList: List<GetCredentialProviderData>
     get() = this.extras?.getParcelableArrayList(
         ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
         GetCredentialProviderData::class.java
-    ) ?: emptyList()
+    ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList()
 
-val Intent.createCredentialProviderDataList: List<ProviderData>
+val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData>
     get() = this.extras?.getParcelableArrayList(
         ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
         CreateCredentialProviderData::class.java
-    ) ?: emptyList()
+    ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList()
 
 val Intent.resultReceiver: ResultReceiver?
     get() = this.getParcelableExtra(
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
deleted file mode 100644
index 99dc9ec..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
+++ /dev/null
@@ -1,36 +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.0N
- *
- * 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.credentialmanager.mapper
-
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.util.Log
-import com.android.credentialmanager.TAG
-import com.android.credentialmanager.ktx.appLabel
-import com.android.credentialmanager.ktx.cancelUiRequest
-import com.android.credentialmanager.model.Request
-
-fun Intent.toRequestCancel(packageManager: PackageManager): Request.Cancel? =
-    this.cancelUiRequest?.let { cancelUiRequest ->
-        val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName)
-        if (appLabel == null) {
-            Log.d(TAG, "Received UI cancel request with an invalid package name.")
-            null
-        } else {
-            Request.Cancel(appName = appLabel)
-        }
-    }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
deleted file mode 100644
index 02ee77b..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
+++ /dev/null
@@ -1,49 +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.0N
- *
- * 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.credentialmanager.mapper
-
-import android.content.Intent
-import com.android.credentialmanager.ktx.cancelUiRequest
-import com.android.credentialmanager.ktx.requestInfo
-import com.android.credentialmanager.model.Request
-
-fun Intent.toRequestClose(
-    previousIntent: Intent? = null,
-): Request.Close? {
-    // Close request comes as "Cancel" request from Credential Manager API
-    this.cancelUiRequest?.let { cancelUiRequest ->
-
-        if (cancelUiRequest.shouldShowCancellationUi()) {
-            // Current request is to Cancel and not to Close
-            return null
-        }
-
-        previousIntent?.let {
-            val previousToken = previousIntent.requestInfo?.token
-            val currentToken = this.requestInfo?.token
-
-            if (previousToken != currentToken) {
-                // Current cancellation is for a different request, don't close the current flow.
-                return null
-            }
-        }
-
-        return Request.Close
-    }
-
-    return null
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index ee45fbb..d4bca2a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -18,7 +18,6 @@
 
 import android.content.Intent
 import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
 import androidx.credentials.provider.PasswordCredentialEntry
 import com.android.credentialmanager.factory.fromSlice
 import com.android.credentialmanager.ktx.getCredentialProviderDataList
@@ -32,12 +31,10 @@
 fun Intent.toGet(): Request.Get {
     val credentialEntries = mutableListOf<Pair<String, Entry>>()
     for (providerData in getCredentialProviderDataList) {
-        if (providerData is GetCredentialProviderData) {
-            for (credentialEntry in providerData.credentialEntries) {
-                credentialEntries.add(
-                    Pair(providerData.providerFlattenedComponentName, credentialEntry)
-                )
-            }
+        for (credentialEntry in providerData.credentialEntries) {
+            credentialEntries.add(
+                Pair(providerData.providerFlattenedComponentName, credentialEntry)
+            )
         }
     }
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index ed98f3e..2289ed7 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,33 +25,40 @@
 /**
  * Represents the request made by the CredentialManager API.
  */
-sealed class Request {
+sealed class Request private constructor(
+    open val token: IBinder?,
+) {
 
     /**
      * Request to close the app without displaying a message to the user and without reporting
      * anything back to the Credential Manager service.
      */
-    data object Close : Request()
+    data class Close(
+        override val token: IBinder?,
+    ) : Request(token)
 
     /**
      * Request to close the app, displaying a message to the user.
      */
     data class Cancel(
-        val appName: String
-    ) : Request()
+        val appName: String,
+        override val token: IBinder?,
+    ) : Request(token)
 
     /**
      * Request to start the get credentials flow.
      */
     data class Get(
-        val token: IBinder?,
+        override val token: IBinder?,
         val resultReceiver: ResultReceiver?,
         val providers: ImmutableMap<String, ProviderData>,
         val passwordEntries: ImmutableList<Password>,
-    ) : Request()
-
+    ) : Request(token)
     /**
      * Request to start the create credentials flow.
      */
-    data object Create : Request()
+    data class Create(
+        override val token: IBinder?,
+    ) : Request(token)
 }
+
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
deleted file mode 100644
index 5738fee..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
+++ /dev/null
@@ -1,57 +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.0N
- *
- * 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.credentialmanager.repository
-
-import android.content.Intent
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.ProviderPendingIntentResponse
-import android.credentials.ui.UserSelectionDialogResult
-import android.os.Bundle
-import android.util.Log
-import com.android.credentialmanager.TAG
-import com.android.credentialmanager.model.Password
-import com.android.credentialmanager.model.Request
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class PasswordRepository @Inject constructor() {
-
-    suspend fun selectPassword(
-        password: Password,
-        request: Request.Get,
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        Log.d(TAG, "password selected: {provider=${password.providerId}" +
-            ", key=${password.entry.key}, subkey=${password.entry.subkey}}")
-
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            request.token,
-            password.providerId,
-            password.entry.key,
-            password.entry.subkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        val resultDataBundle = Bundle()
-        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
-        request.resultReceiver?.send(
-            BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
-            resultDataBundle
-        )
-    }
-}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
deleted file mode 100644
index 1973fc1..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
+++ /dev/null
@@ -1,48 +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.0N
- *
- * 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.credentialmanager.repository
-
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.util.Log
-import com.android.credentialmanager.TAG
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.parse
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class RequestRepository @Inject constructor(
-        private val packageManager: PackageManager,
-) {
-
-    private val _requests = MutableStateFlow<Request?>(null)
-    val requests: StateFlow<Request?> = _requests
-
-    suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) {
-        val request = intent.parse(
-            packageManager = packageManager,
-            previousIntent = previousIntent
-        )
-
-        Log.d(TAG, "Request parsed: $request")
-
-        _requests.value = request
-    }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index f2df64a..0df40d7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,6 +25,7 @@
 import com.android.credentialmanager.ui.WearApp
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import dagger.hilt.android.AndroidEntryPoint
+import kotlin.system.exitProcess
 
 @AndroidEntryPoint(ComponentActivity::class)
 class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
@@ -34,25 +35,21 @@
     @OptIn(ExperimentalHorologistApi::class)
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-
         setTheme(android.R.style.Theme_DeviceDefault)
         setContent {
             MaterialTheme {
                 WearApp(
                     viewModel = viewModel,
-                    onCloseApp = ::finish,
+                    onCloseApp = { exitProcess(0) },
                 )
             }
         }
-        viewModel.onNewIntent(intent)
+        viewModel.updateRequest(intent)
     }
 
     override fun onNewIntent(intent: Intent) {
         super.onNewIntent(intent)
-
-        val previousIntent = getIntent()
         setIntent(intent)
-
-        viewModel.onNewIntent(intent, previousIntent)
+        viewModel.updateRequest(intent)
     }
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 435cd37..2a7e9e1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -20,28 +20,27 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.repository.RequestRepository
+import com.android.credentialmanager.client.CredentialManagerClient
 import com.android.credentialmanager.ui.mappers.toGet
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
 class CredentialSelectorViewModel @Inject constructor(
-    private val requestRepository: RequestRepository,
+    private val credentialManagerClient: CredentialManagerClient,
 ) : ViewModel() {
 
-    val uiState: StateFlow<CredentialSelectorUiState> = requestRepository.requests
+    val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
         .map { request ->
             when (request) {
                 null -> CredentialSelectorUiState.Idle
                 is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
-                Request.Close -> CredentialSelectorUiState.Close
-                Request.Create -> CredentialSelectorUiState.Create
+                is Request.Close -> CredentialSelectorUiState.Close
+                is Request.Create -> CredentialSelectorUiState.Create
                 is Request.Get -> request.toGet()
             }
         }
@@ -51,10 +50,8 @@
             initialValue = CredentialSelectorUiState.Idle,
         )
 
-    fun onNewIntent(intent: Intent, previousIntent: Intent? = null) {
-        viewModelScope.launch {
-            requestRepository.processRequest(intent = intent, previousIntent = previousIntent)
-        }
+    fun updateRequest(intent: Intent) {
+            credentialManagerClient.updateRequest(intent = intent)
     }
 }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
index cb1a4a1..6ededf3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
@@ -2,17 +2,28 @@
 
 import android.content.Context
 import android.content.pm.PackageManager
+import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.client.impl.CredentialManagerClientImpl
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
 import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
 @Module
 @InstallIn(SingletonComponent::class)
 internal object AppModule {
     @Provides
+    @Singleton
     @JvmStatic
     fun providePackageManager(@ApplicationContext context: Context): PackageManager =
-            context.packageManager
+        context.packageManager
+
+    @Provides
+    @Singleton
+    @JvmStatic
+    fun provideCredentialManagerClient(packageManager: PackageManager): CredentialManagerClient =
+        CredentialManagerClientImpl(packageManager)
 }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt
index da5697d..77fb3e7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt
@@ -19,9 +19,14 @@
 import androidx.navigation.NavController
 
 fun NavController.navigateToLoading() {
-    navigate(Screen.Loading.route)
+    navigateToAsRoot(Screen.Loading.route)
 }
 
 fun NavController.navigateToSinglePasswordScreen() {
-    navigate(Screen.SinglePasswordScreen.route)
+    navigateToAsRoot(Screen.SinglePasswordScreen.route)
+}
+
+fun NavController.navigateToAsRoot(route: String) {
+    popBackStack()
+    navigate(route)
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 43514a0..fb72c54 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -17,6 +17,8 @@
 package com.android.credentialmanager.ui.screens.single.password
 
 import android.content.Intent
+import android.credentials.ui.ProviderPendingIntentResponse
+import android.credentials.ui.UserSelectionDialogResult
 import android.util.Log
 import androidx.activity.result.IntentSenderRequest
 import androidx.annotation.MainThread
@@ -26,20 +28,17 @@
 import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Password
 import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.repository.PasswordRepository
-import com.android.credentialmanager.repository.RequestRepository
+import com.android.credentialmanager.client.CredentialManagerClient
 import com.android.credentialmanager.ui.model.PasswordUiModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
 class SinglePasswordScreenViewModel @Inject constructor(
-    private val requestRepository: RequestRepository,
-    private val passwordRepository: PasswordRepository,
+    private val credentialManagerClient: CredentialManagerClient,
 ) : ViewModel() {
 
     private var initializeCalled = false
@@ -57,8 +56,8 @@
         initializeCalled = true
 
         viewModelScope.launch {
-            val request = requestRepository.requests.first()
-            Log.d(TAG, "request: $request")
+            val request = credentialManagerClient.requests.value
+            Log.d(TAG, "request: $request, client instance: $credentialManagerClient")
 
             if (request !is Request.Get) {
                 _uiState.value = SinglePasswordScreenUiState.Error
@@ -93,16 +92,15 @@
         resultCode: Int? = null,
         resultData: Intent? = null,
     ) {
-        viewModelScope.launch {
-            passwordRepository.selectPassword(
-                password = password,
-                request = requestGet,
-                resultCode = resultCode,
-                resultData = resultData
-            )
-
-            _uiState.value = SinglePasswordScreenUiState.Completed
-        }
+        val userSelectionDialogResult = UserSelectionDialogResult(
+            requestGet.token,
+            password.providerId,
+            password.entry.key,
+            password.entry.subkey,
+            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+        )
+        credentialManagerClient.sendResult(userSelectionDialogResult)
+        _uiState.value = SinglePasswordScreenUiState.Completed
     }
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 93a5082..071f9f4 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -116,6 +116,9 @@
     base:                               'w'
     shift, capslock:                    'W'
     shift+capslock:                     'w'
+    ralt:                               '\u1e83'
+    shift+ralt, capslock+ralt:          '\u1e82'
+    shift+capslock+ralt:                '\u1e83'
 }
 
 key E {
@@ -147,6 +150,9 @@
     base:                               'y'
     shift, capslock:                    'Y'
     shift+capslock:                     'y'
+    ralt:                               '\u00fd'
+    shift+ralt, capslock+ralt:          '\u00dd'
+    shift+capslock+ralt:                '\u00fd'
 }
 
 key U {
@@ -313,6 +319,9 @@
     base:                               'c'
     shift, capslock:                    'C'
     shift+capslock:                     'c'
+    ralt:                               '\u00e7'
+    shift+ralt, capslock+ralt:          '\u00c7'
+    shift+capslock+ralt:                '\u00e7'
 }
 
 key V {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 4906304..636f98d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -44,7 +44,7 @@
     label:                              '2'
     base:                               '\u00e9'
     shift:                              '2'
-    ralt:                               '~'
+    ralt:                               '\u0303'
 }
 
 key 3 {
@@ -79,7 +79,7 @@
     label:                              '7'
     base:                               '\u00e8'
     shift:                              '7'
-    ralt:                               '`'
+    ralt:                               '\u0300'
 }
 
 key 8 {
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 7f23f74..ee49b23 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -94,7 +94,7 @@
         android:name="keyboard_layout_swiss_german"
         android:label="@string/keyboard_layout_swiss_german_label"
         android:keyboardLayout="@raw/keyboard_layout_swiss_german"
-        android:keyboardLocale="de-Latn-CH"
+        android:keyboardLocale="de-Latn-CH|gsw-Latn-CH"
         android:keyboardLayoutType="qwertz" />
 
     <keyboard-layout
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 35f5772..ef218fd 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -43,10 +43,18 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="v2.model.TemporaryFileManager"
+            android:exported="false"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
         <activity android:name=".v2.ui.InstallLaunch"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:theme="@style/Theme.AlertDialogActivity"
-            android:exported="true"/>
+            android:exported="false"/>
 
         <activity android:name=".InstallStart"
                 android:theme="@style/Theme.AlertDialogActivity"
@@ -86,6 +94,7 @@
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
+                  android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
                   android:exported="false" />
 
         <activity android:name=".InstallInstalling"
@@ -100,6 +109,15 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".v2.model.InstallEventReceiver"
+            android:permission="android.permission.INSTALL_PACKAGES"
+            android:exported="false"
+            android:enabled="false">
+            <intent-filter android:priority="1">
+                <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
+            </intent-filter>
+        </receiver>
+
         <activity android:name=".InstallSuccess"
                 android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
                 android:exported="false" />
@@ -122,6 +140,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".v2.ui.UninstallLaunch"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
+            android:excludeFromRecents="true"
+            android:noHistory="true"
+            android:exported="false">
+        </activity>
+
         <receiver android:name=".UninstallEventReceiver"
             android:permission="android.permission.INSTALL_PACKAGES"
             android:exported="false">
@@ -130,6 +156,15 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".v2.model.UninstallEventReceiver"
+            android:permission="android.permission.INSTALL_PACKAGES"
+            android:exported="false"
+            android:enabled="false">
+            <intent-filter android:priority="1">
+                <action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name=".PackageInstalledReceiver"
                 android:exported="false">
             <intent-filter android:priority="1">
@@ -163,6 +198,18 @@
 
         <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
             tools:node="remove" />
+
+        <activity android:name=".UnarchiveActivity"
+                  android:configChanges="orientation|keyboardHidden|screenSize"
+                  android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
+                  android:excludeFromRecents="true"
+                  android:noHistory="true"
+                  android:exported="true">
+            <intent-filter android:priority="1">
+                <action android:name="android.intent.action.UNARCHIVE_DIALOG" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 
 </manifest>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 4eaa39b..0a2e880 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -257,4 +257,14 @@
     <!-- Notification shown in status bar when an application is successfully installed.
          [CHAR LIMIT=50] -->
     <string name="notification_installation_success_status">Successfully installed \u201c<xliff:g id="appname" example="Package Installer">%1$s</xliff:g>\u201d</string>
+
+    <!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
+         after parts of the app have been previously moved into the cloud for temporary storage.
+         "installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
+    <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+    <!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
+    <string name="unarchive_body_text">This app will begin to download in the background</string>
+    <!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
+         into the cloud for temporary storage. [CHAR LIMIT=15] -->
+    <string name="restore">Restore</string>
 </resources>
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index aa1fa16..811fa73 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,8 +32,8 @@
         <item name="android:windowNoTitle">true</item>
     </style>
 
-    <style name="Theme.AlertDialogActivity.NoDim">
-        <item name="android:windowNoTitle">true</item>
+    <style name="Theme.AlertDialogActivity.NoDim"
+        parent="@style/Theme.AlertDialogActivity.NoActionBar">
         <item name="android:backgroundDimAmount">0</item>
     </style>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 19d74b3..7b17cbd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,8 +16,6 @@
 
 package com.android.packageinstaller;
 
-import static android.content.Intent.CATEGORY_LAUNCHER;
-
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.Activity;
@@ -47,9 +45,6 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         setResult(resultCode, data);
         finish();
-        if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
-            startActivity(data);
-        }
     }
 
     @Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 74f04e0..eef21991 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -36,12 +36,14 @@
 /**
  * Installation failed: Return status code to the caller or display failure UI to user
  */
-public class InstallFailed extends AlertActivity {
+public class InstallFailed extends Activity {
     private static final String LOG_TAG = InstallFailed.class.getSimpleName();
 
     /** Label of the app that failed to install */
     private CharSequence mLabel;
 
+    private AlertDialog mDialog;
+
     /**
      * Unhide the appropriate label for the statusCode.
      *
@@ -53,19 +55,19 @@
         View viewToEnable;
         switch (statusCode) {
             case PackageInstaller.STATUS_FAILURE_BLOCKED:
-                viewToEnable = requireViewById(R.id.install_failed_blocked);
+                viewToEnable = mDialog.requireViewById(R.id.install_failed_blocked);
                 break;
             case PackageInstaller.STATUS_FAILURE_CONFLICT:
-                viewToEnable = requireViewById(R.id.install_failed_conflict);
+                viewToEnable = mDialog.requireViewById(R.id.install_failed_conflict);
                 break;
             case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
-                viewToEnable = requireViewById(R.id.install_failed_incompatible);
+                viewToEnable = mDialog.requireViewById(R.id.install_failed_incompatible);
                 break;
             case PackageInstaller.STATUS_FAILURE_INVALID:
-                viewToEnable = requireViewById(R.id.install_failed_invalid_apk);
+                viewToEnable = mDialog.requireViewById(R.id.install_failed_invalid_apk);
                 break;
             default:
-                viewToEnable = requireViewById(R.id.install_failed);
+                viewToEnable = mDialog.requireViewById(R.id.install_failed);
                 break;
         }
 
@@ -105,12 +107,18 @@
             // Store label for dialog
             mLabel = as.label;
 
-            mAlert.setIcon(as.icon);
-            mAlert.setTitle(as.label);
-            mAlert.setView(R.layout.install_content_view);
-            mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.done),
-                    (ignored, ignored2) -> finish(), null);
-            setupAlert();
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+            builder.setIcon(as.icon);
+            builder.setTitle(as.label);
+            builder.setView(R.layout.install_content_view);
+            builder.setPositiveButton(getString(R.string.done),
+                    (ignored, ignored2) -> finish());
+            builder.setOnCancelListener(dialog -> {
+                finish();
+            });
+            mDialog = builder.create();
+            mDialog.show();
 
             // Show out of space dialog if needed
             if (statusCode == PackageInstaller.STATUS_FAILURE_STORAGE) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 4992ef1..daedb1a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -19,6 +19,8 @@
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET;
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
+import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -43,7 +45,7 @@
  * <p>This has two phases: First send the data to the package manager, then wait until the package
  * manager processed the result.</p>
  */
-public class InstallInstalling extends AlertActivity {
+public class InstallInstalling extends Activity {
     private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
 
     private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
@@ -67,6 +69,8 @@
     /** The button that can cancel this dialog */
     private Button mCancelButton;
 
+    private AlertDialog mDialog;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -90,10 +94,12 @@
             PackageUtil.AppSnippet as = getIntent()
                     .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
 
-            mAlert.setIcon(as.icon);
-            mAlert.setTitle(as.label);
-            mAlert.setView(R.layout.install_content_view);
-            mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+            builder.setIcon(as.icon);
+            builder.setTitle(as.label);
+            builder.setView(R.layout.install_content_view);
+            builder.setNegativeButton(getString(R.string.cancel),
                     (ignored, ignored2) -> {
                         if (mInstallingTask != null) {
                             mInstallingTask.cancel(true);
@@ -106,9 +112,11 @@
 
                         setResult(RESULT_CANCELED);
                         finish();
-                    }, null);
-            setupAlert();
-            requireViewById(R.id.installing).setVisibility(View.VISIBLE);
+                    });
+            builder.setCancelable(false);
+            mDialog = builder.create();
+            mDialog.show();
+            mDialog.requireViewById(R.id.installing).setVisibility(View.VISIBLE);
 
             if (savedInstanceState != null) {
                 mSessionId = savedInstanceState.getInt(SESSION_ID);
@@ -145,7 +153,7 @@
                 }
             }
 
-            mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
+            mCancelButton = mDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
         }
     }
 
@@ -290,14 +298,7 @@
                         broadcastIntent,
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
-                try {
-                    session.commit(pendingIntent.getIntentSender());
-                } catch (Exception e) {
-                    Log.e(LOG_TAG, "Cannot install package: ", e);
-                    launchFailure(PackageInstaller.STATUS_FAILURE,
-                        PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
-                    return;
-                }
+                session.commit(pendingIntent.getIntentSender());
                 mCancelButton.setEnabled(false);
                 setFinishOnTouchOutside(false);
             } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 483fb8c..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -52,7 +52,7 @@
  * If a package gets installed from a content URI this step stages the installation session
  * reading bytes from the URI.
  */
-public class InstallStaging extends AlertActivity {
+public class InstallStaging extends Activity {
     private static final String LOG_TAG = InstallStaging.class.getSimpleName();
 
     private static final String STAGED_SESSION_ID = "STAGED_SESSION_ID";
@@ -65,6 +65,8 @@
     /** The session the package is in */
     private int mStagedSessionId;
 
+    private AlertDialog mDialog;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -72,10 +74,13 @@
         mInstaller = getPackageManager().getPackageInstaller();
 
         setFinishOnTouchOutside(true);
-        mAlert.setIcon(R.drawable.ic_file_download);
-        mAlert.setTitle(getString(R.string.app_name_unknown));
-        mAlert.setView(R.layout.install_content_view);
-        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+        builder.setIcon(R.drawable.ic_file_download);
+        builder.setTitle(getString(R.string.app_name_unknown));
+        builder.setView(R.layout.install_content_view);
+        builder.setNegativeButton(getString(R.string.cancel),
                 (ignored, ignored2) -> {
                     if (mStagingTask != null) {
                         mStagingTask.cancel(true);
@@ -85,9 +90,21 @@
 
                     setResult(RESULT_CANCELED);
                     finish();
-                }, null);
-        setupAlert();
-        requireViewById(R.id.staging).setVisibility(View.VISIBLE);
+                });
+        builder.setOnCancelListener(dialog -> {
+            if (mStagingTask != null) {
+                mStagingTask.cancel(true);
+            }
+
+            cleanupStagingSession();
+
+            setResult(RESULT_CANCELED);
+            finish();
+        });
+        mDialog = builder.create();
+        mDialog.show();
+        mDialog.requireViewById(com.android.packageinstaller.R.id.staging)
+            .setVisibility(View.VISIBLE);
 
         if (savedInstanceState != null) {
             mStagedSessionId = savedInstanceState.getInt(STAGED_SESSION_ID, 0);
@@ -275,8 +292,9 @@
         @Override
         protected void onPreExecute() {
             final long sizeBytes = getContentSizeBytes();
-
-            mProgressBar = sizeBytes > 0 ? requireViewById(R.id.progress_indeterminate) : null;
+            if (sizeBytes > 0 && mDialog != null) {
+                mProgressBar = mDialog.requireViewById(R.id.progress_indeterminate);
+            }
             if (mProgressBar != null) {
                 mProgressBar.setProgress(0);
                 mProgressBar.setMax(100);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index fbc9525..215ead3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,6 +17,8 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -34,7 +36,7 @@
 /**
  * Finish installation: Return status code to the caller or display "success" UI to user
  */
-public class InstallSuccess extends AlertActivity {
+public class InstallSuccess extends Activity {
     private static final String LOG_TAG = InstallSuccess.class.getSimpleName();
 
     @Nullable
@@ -46,6 +48,8 @@
     @Nullable
     private Intent mLaunchIntent;
 
+    private AlertDialog mDialog;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -83,20 +87,27 @@
             return;
         }
 
-        mAlert.setIcon(mAppSnippet.icon);
-        mAlert.setTitle(mAppSnippet.label);
-        mAlert.setView(R.layout.install_content_view);
-        mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.launch), null,
-                null);
-        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.done),
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setIcon(mAppSnippet.icon);
+        builder.setTitle(mAppSnippet.label);
+        builder.setView(R.layout.install_content_view);
+        builder.setPositiveButton(getString(R.string.launch), null);
+        builder.setNegativeButton(getString(R.string.done),
                 (ignored, ignored2) -> {
                     if (mAppPackageName != null) {
                         Log.i(LOG_TAG, "Finished installing " + mAppPackageName);
                     }
                     finish();
-                }, null);
-        setupAlert();
-        requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
+                });
+        builder.setOnCancelListener(dialog -> {
+            if (mAppPackageName != null) {
+                Log.i(LOG_TAG, "Finished installing " + mAppPackageName);
+            }
+            finish();
+        });
+        mDialog = builder.create();
+        mDialog.show();
+        mDialog.requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
         // Enable or disable "launch" button
         boolean enabled = false;
         if (mLaunchIntent != null) {
@@ -107,10 +118,15 @@
             }
         }
 
-        Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
         if (enabled) {
             launchButton.setOnClickListener(view -> {
-                setResult(Activity.RESULT_OK, mLaunchIntent);
+                try {
+                    startActivity(mLaunchIntent.addFlags(
+                        Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP));
+                } catch (ActivityNotFoundException | SecurityException e) {
+                    Log.e(LOG_TAG, "Could not start activity", e);
+                }
                 finish();
             });
         } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index c5ae4a3..ceb580d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -71,7 +71,7 @@
  * Based on the user response the package is then installed by launching InstallAppConfirm
  * sub activity. All state transitions are handled in this activity
  */
-public class PackageInstallerActivity extends AlertActivity {
+public class PackageInstallerActivity extends Activity {
     private static final String TAG = "PackageInstaller";
 
     private static final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
@@ -135,11 +135,13 @@
     // Would the mOk button be enabled if this activity would be resumed
     private boolean mEnableOk = false;
 
+    private AlertDialog mDialog;
+
     private void startInstallConfirm() {
         TextView viewToEnable;
 
         if (mAppInfo != null) {
-            viewToEnable = requireViewById(R.id.install_confirm_question_update);
+            viewToEnable = mDialog.requireViewById(R.id.install_confirm_question_update);
 
             final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel();
             final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
@@ -157,7 +159,7 @@
             }
         } else {
             // This is a new application with no permissions.
-            viewToEnable = requireViewById(R.id.install_confirm_question);
+            viewToEnable = mDialog.requireViewById(R.id.install_confirm_question);
         }
 
         viewToEnable.setVisibility(View.VISIBLE);
@@ -480,10 +482,11 @@
     }
 
     private void bindUi() {
-        mAlert.setIcon(mAppSnippet.icon);
-        mAlert.setTitle(mAppSnippet.label);
-        mAlert.setView(R.layout.install_content_view);
-        mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setIcon(mAppSnippet.icon);
+        builder.setTitle(mAppSnippet.label);
+        builder.setView(R.layout.install_content_view);
+        builder.setPositiveButton(getString(R.string.install),
                 (ignored, ignored2) -> {
                     if (mOk.isEnabled()) {
                         if (mSessionId != -1) {
@@ -493,20 +496,26 @@
                             startInstall();
                         }
                     }
-                }, null);
-        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
+                });
+        builder.setNegativeButton(getString(R.string.cancel),
                 (ignored, ignored2) -> {
                     // Cancel and finish
                     setActivityResult(RESULT_CANCELED);
                     finish();
-                }, null);
-        setupAlert();
+                });
+        builder.setOnCancelListener(dialog -> {
+            // Cancel and finish
+            setActivityResult(RESULT_CANCELED);
+            finish();
+        });
+        mDialog = builder.create();
+        mDialog.show();
 
-        mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        mOk = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
         mOk.setEnabled(false);
 
         if (!mOk.isInTouchMode()) {
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
+            mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
         }
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
new file mode 100644
index 0000000..754437e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -0,0 +1,151 @@
+/*
+ * 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;
+
+import static android.Manifest.permission;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class UnarchiveActivity extends Activity {
+
+    public static final String EXTRA_UNARCHIVE_INTENT_SENDER =
+            "android.content.pm.extra.UNARCHIVE_INTENT_SENDER";
+    static final String APP_TITLE = "com.android.packageinstaller.unarchive.app_title";
+    static final String INSTALLER_TITLE = "com.android.packageinstaller.unarchive.installer_title";
+
+    private static final String TAG = "UnarchiveActivity";
+
+    private String mPackageName;
+    private IntentSender mIntentSender;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(null);
+
+        int callingUid = getLaunchedFromUid();
+        if (callingUid == Process.INVALID_UID) {
+            // Cannot reach Package/ActivityManager. Aborting uninstall.
+            Log.e(TAG, "Could not determine the launching uid.");
+
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        }
+
+        String callingPackage = getPackageNameForUid(callingUid);
+        if (callingPackage == null) {
+            Log.e(TAG, "Package not found for originating uid " + callingUid);
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        }
+
+        // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester
+        // is not the source of the installation.
+        boolean hasRequestInstallPermission = Arrays.asList(getRequestedPermissions(callingPackage))
+                .contains(permission.REQUEST_INSTALL_PACKAGES);
+        boolean hasInstallPermission = getBaseContext().checkPermission(permission.INSTALL_PACKAGES,
+                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED;
+        if (!hasRequestInstallPermission && !hasInstallPermission) {
+            Log.e(TAG, "Uid " + callingUid + " does not have "
+                    + permission.REQUEST_INSTALL_PACKAGES + " or "
+                    + permission.INSTALL_PACKAGES);
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        }
+
+        Bundle extras = getIntent().getExtras();
+        mPackageName = extras.getString(PackageInstaller.EXTRA_PACKAGE_NAME);
+        mIntentSender = extras.getParcelable(EXTRA_UNARCHIVE_INTENT_SENDER, IntentSender.class);
+        Objects.requireNonNull(mPackageName);
+        Objects.requireNonNull(mIntentSender);
+
+        PackageManager pm = getPackageManager();
+        try {
+            String appTitle = pm.getApplicationInfo(mPackageName,
+                    PackageManager.ApplicationInfoFlags.of(
+                            MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
+            // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
+            //  archived apps.
+            showDialogFragment(appTitle, "installerTitle");
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Invalid packageName: " + e.getMessage());
+        }
+    }
+
+    @Nullable
+    private String[] getRequestedPermissions(String callingPackage) {
+        String[] requestedPermissions = null;
+        try {
+            requestedPermissions = getPackageManager()
+                    .getPackageInfo(callingPackage, GET_PERMISSIONS).requestedPermissions;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Should be unreachable because we've just fetched the packageName above.
+            Log.e(TAG, "Package not found for " + callingPackage);
+        }
+        return requestedPermissions;
+    }
+
+    void startUnarchive() {
+        try {
+            getPackageManager().getPackageInstaller().requestUnarchive(mPackageName, mIntentSender);
+        } catch (PackageManager.NameNotFoundException | IOException e) {
+            Log.e(TAG, "RequestUnarchive failed with %s." + e.getMessage());
+        }
+    }
+
+    private void showDialogFragment(String appTitle, String installerAppTitle) {
+        FragmentTransaction ft = getFragmentManager().beginTransaction();
+        Fragment prev = getFragmentManager().findFragmentByTag("dialog");
+        if (prev != null) {
+            ft.remove(prev);
+        }
+
+        Bundle args = new Bundle();
+        args.putString(APP_TITLE, appTitle);
+        args.putString(INSTALLER_TITLE, installerAppTitle);
+        DialogFragment fragment = new UnarchiveFragment();
+        fragment.setArguments(args);
+        fragment.show(ft, "dialog");
+    }
+
+    private String getPackageNameForUid(int sourceUid) {
+        String[] packagesForUid = getPackageManager().getPackagesForUid(sourceUid);
+        if (packagesForUid == null) {
+            return null;
+        }
+        return packagesForUid[0];
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
new file mode 100644
index 0000000..6ccbc4c
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+public class UnarchiveFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        String appTitle = getArguments().getString(UnarchiveActivity.APP_TITLE);
+
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
+
+        dialogBuilder.setTitle(
+                String.format(getContext().getString(R.string.unarchive_application_title),
+                        appTitle));
+        dialogBuilder.setMessage(R.string.unarchive_body_text);
+
+        dialogBuilder.setPositiveButton(R.string.restore, this);
+        dialogBuilder.setNegativeButton(android.R.string.cancel, this);
+
+        return dialogBuilder.create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == Dialog.BUTTON_POSITIVE) {
+            ((UnarchiveActivity) getActivity()).startUnarchive();
+        }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        if (isAdded()) {
+            getActivity().finish();
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 9c67817..34062a4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -56,6 +56,7 @@
 import com.android.packageinstaller.television.UninstallAlertFragment;
 import com.android.packageinstaller.television.UninstallAppProgress;
 
+import com.android.packageinstaller.v2.ui.UninstallLaunch;
 import java.util.List;
 
 /*
@@ -80,6 +81,9 @@
     private String mPackageName;
     private DialogInfo mDialogInfo;
 
+    // TODO (sumedhsen): Replace with an Android Feature Flag once implemented
+    private static final boolean USE_PIA_V2 = false;
+
     @Override
     public void onCreate(Bundle icicle) {
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
@@ -88,6 +92,20 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
+        if (USE_PIA_V2 && !isTv()) {
+            boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
+            Intent piaV2 = new Intent(getIntent());
+            piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid());
+            piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_ACTIVITY_NAME, getCallingActivity());
+            if (returnResult) {
+                piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+            }
+            piaV2.setClass(this, UninstallLaunch.class);
+            startActivity(piaV2);
+            finish();
+            return;
+        }
+
         int callingUid = getLaunchedFromUid();
         if (callingUid == Process.INVALID_UID) {
             // Cannot reach Package/ActivityManager. Aborting uninstall.
@@ -176,7 +194,8 @@
 
         try {
             mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
-                    PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
+                    PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER
+                            | PackageManager.MATCH_ARCHIVED_PACKAGES));
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Unable to get packageName. Package manager is dead?");
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 4a93bf8..d113878 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -189,7 +189,8 @@
 
         boolean suggestToKeepAppData;
         try {
-            PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0);
+            PackageInfo pkgInfo = pm.getPackageInfo(pkg,
+                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES));
 
             suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java
new file mode 100644
index 0000000..4d2d911
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java
@@ -0,0 +1,378 @@
+/*
+ * 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.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.os.AsyncTask;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+/**
+ * Persists results of events and calls back observers when a matching result arrives.
+ */
+public class EventResultPersister {
+
+    /**
+     * Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id
+     */
+    public static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
+    /**
+     * The extra with the id to set in the intent delivered to
+     * {@link #onEventReceived(Context, Intent)}
+     */
+    public static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+    public static final String EXTRA_SERVICE_ID = "EventResultPersister.EXTRA_SERVICE_ID";
+    private static final String TAG = EventResultPersister.class.getSimpleName();
+    /**
+     * Persisted state of this object
+     */
+    private final AtomicFile mResultsFile;
+
+    private final Object mLock = new Object();
+
+    /**
+     * Currently stored but not yet called back results (install id -> status, status message)
+     */
+    private final SparseArray<EventResult> mResults = new SparseArray<>();
+
+    /**
+     * Currently registered, not called back observers (install id -> observer)
+     */
+    private final SparseArray<EventResultObserver> mObservers = new SparseArray<>();
+
+    /**
+     * Always increasing counter for install event ids
+     */
+    private int mCounter;
+
+    /**
+     * If a write that will persist the state is scheduled
+     */
+    private boolean mIsPersistScheduled;
+
+    /**
+     * If the state was changed while the data was being persisted
+     */
+    private boolean mIsPersistingStateValid;
+
+    /**
+     * Read persisted state.
+     *
+     * @param resultFile The file the results are persisted in
+     */
+    EventResultPersister(@NonNull File resultFile) {
+        mResultsFile = new AtomicFile(resultFile);
+        mCounter = GENERATE_NEW_ID + 1;
+
+        try (FileInputStream stream = mResultsFile.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+            nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if ("results".equals(tagName)) {
+                    mCounter = readIntAttribute(parser, "counter");
+                } else if ("result".equals(tagName)) {
+                    int id = readIntAttribute(parser, "id");
+                    int status = readIntAttribute(parser, "status");
+                    int legacyStatus = readIntAttribute(parser, "legacyStatus");
+                    String statusMessage = readStringAttribute(parser, "statusMessage");
+                    int serviceId = readIntAttribute(parser, "serviceId");
+
+                    if (mResults.get(id) != null) {
+                        throw new Exception("id " + id + " has two results");
+                    }
+
+                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage,
+                        serviceId));
+                } else {
+                    throw new Exception("unexpected tag");
+                }
+
+                nextElement(parser);
+            }
+        } catch (Exception e) {
+            mResults.clear();
+            writeState();
+        }
+    }
+
+    /**
+     * Progress parser to the next element.
+     *
+     * @param parser The parser to progress
+     */
+    private static void nextElement(@NonNull XmlPullParser parser)
+        throws XmlPullParserException, IOException {
+        int type;
+        do {
+            type = parser.next();
+        } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
+    }
+
+    /**
+     * Read an int attribute from the current element
+     *
+     * @param parser The parser to read from
+     * @param name The attribute name to read
+     * @return The value of the attribute
+     */
+    private static int readIntAttribute(@NonNull XmlPullParser parser, @NonNull String name) {
+        return Integer.parseInt(parser.getAttributeValue(null, name));
+    }
+
+    /**
+     * Read an String attribute from the current element
+     *
+     * @param parser The parser to read from
+     * @param name The attribute name to read
+     * @return The value of the attribute or null if the attribute is not set
+     */
+    private static String readStringAttribute(@NonNull XmlPullParser parser, @NonNull String name) {
+        return parser.getAttributeValue(null, name);
+    }
+
+    /**
+     * @return a new event id.
+     */
+    public int getNewId() throws OutOfIdsException {
+        synchronized (mLock) {
+            if (mCounter == Integer.MAX_VALUE) {
+                throw new OutOfIdsException();
+            }
+
+            mCounter++;
+            writeState();
+
+            return mCounter - 1;
+        }
+    }
+
+    /**
+     * Add a result. If the result is a pending user action, execute the pending user action
+     * directly and do not queue a result.
+     *
+     * @param context The context the event was received in
+     * @param intent The intent the activity received
+     */
+    void onEventReceived(@NonNull Context context, @NonNull Intent intent) {
+        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
+
+        if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
+            Intent intentToStart = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+            intentToStart.addFlags(FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intentToStart);
+
+            return;
+        }
+
+        int id = intent.getIntExtra(EXTRA_ID, 0);
+        String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+        int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
+        int serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0);
+
+        EventResultObserver observerToCall = null;
+        synchronized (mLock) {
+            int numObservers = mObservers.size();
+            for (int i = 0; i < numObservers; i++) {
+                if (mObservers.keyAt(i) == id) {
+                    observerToCall = mObservers.valueAt(i);
+                    mObservers.removeAt(i);
+
+                    break;
+                }
+            }
+
+            if (observerToCall != null) {
+                observerToCall.onResult(status, legacyStatus, statusMessage, serviceId);
+            } else {
+                mResults.put(id, new EventResult(status, legacyStatus, statusMessage, serviceId));
+                writeState();
+            }
+        }
+    }
+
+    /**
+     * Persist current state. The persistence might be delayed.
+     */
+    private void writeState() {
+        synchronized (mLock) {
+            mIsPersistingStateValid = false;
+
+            if (!mIsPersistScheduled) {
+                mIsPersistScheduled = true;
+
+                AsyncTask.execute(() -> {
+                    int counter;
+                    SparseArray<EventResult> results;
+
+                    while (true) {
+                        // Take snapshot of state
+                        synchronized (mLock) {
+                            counter = mCounter;
+                            results = mResults.clone();
+                            mIsPersistingStateValid = true;
+                        }
+
+                        try (FileOutputStream stream = mResultsFile.startWrite()) {
+                            try {
+                                XmlSerializer serializer = Xml.newSerializer();
+                                serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+                                serializer.startDocument(null, true);
+                                serializer.setFeature(
+                                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                                serializer.startTag(null, "results");
+                                serializer.attribute(null, "counter", Integer.toString(counter));
+
+                                int numResults = results.size();
+                                for (int i = 0; i < numResults; i++) {
+                                    serializer.startTag(null, "result");
+                                    serializer.attribute(null, "id",
+                                        Integer.toString(results.keyAt(i)));
+                                    serializer.attribute(null, "status",
+                                        Integer.toString(results.valueAt(i).status));
+                                    serializer.attribute(null, "legacyStatus",
+                                        Integer.toString(results.valueAt(i).legacyStatus));
+                                    if (results.valueAt(i).message != null) {
+                                        serializer.attribute(null, "statusMessage",
+                                            results.valueAt(i).message);
+                                    }
+                                    serializer.attribute(null, "serviceId",
+                                        Integer.toString(results.valueAt(i).serviceId));
+                                    serializer.endTag(null, "result");
+                                }
+
+                                serializer.endTag(null, "results");
+                                serializer.endDocument();
+
+                                mResultsFile.finishWrite(stream);
+                            } catch (IOException e) {
+                                Log.e(TAG, "error writing results", e);
+                                mResultsFile.failWrite(stream);
+                                mResultsFile.delete();
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "error writing results", e);
+                            mResultsFile.delete();
+                        }
+
+                        // Check if there was changed state since we persisted. If so, we need to
+                        // persist again.
+                        synchronized (mLock) {
+                            if (mIsPersistingStateValid) {
+                                mIsPersistScheduled = false;
+                                break;
+                            }
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * Add an observer. If there is already an event for this id, call back inside of this call.
+     *
+     * @param id The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one.
+     * @param observer The observer to call back.
+     * @return The id for this event
+     */
+    int addObserver(int id, @NonNull EventResultObserver observer)
+        throws OutOfIdsException {
+        synchronized (mLock) {
+            int resultIndex = -1;
+
+            if (id == GENERATE_NEW_ID) {
+                id = getNewId();
+            } else {
+                resultIndex = mResults.indexOfKey(id);
+            }
+
+            // Check if we can instantly call back
+            if (resultIndex >= 0) {
+                EventResult result = mResults.valueAt(resultIndex);
+
+                observer.onResult(result.status, result.legacyStatus, result.message,
+                    result.serviceId);
+                mResults.removeAt(resultIndex);
+                writeState();
+            } else {
+                mObservers.put(id, observer);
+            }
+        }
+
+        return id;
+    }
+
+    /**
+     * Remove a observer.
+     *
+     * @param id The id the observer was added for
+     */
+    void removeObserver(int id) {
+        synchronized (mLock) {
+            mObservers.delete(id);
+        }
+    }
+
+    /**
+     * Call back when a result is received. Observer is removed when onResult it called.
+     */
+    public interface EventResultObserver {
+
+        void onResult(int status, int legacyStatus, @Nullable String message, int serviceId);
+    }
+
+    /**
+     * The status from an event.
+     */
+    private static class EventResult {
+
+        public final int status;
+        public final int legacyStatus;
+        @Nullable
+        public final String message;
+        public final int serviceId;
+
+        private EventResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+            this.status = status;
+            this.legacyStatus = legacyStatus;
+            this.message = message;
+            this.serviceId = serviceId;
+        }
+    }
+
+    public static class OutOfIdsException extends Exception {
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java
new file mode 100644
index 0000000..bcb11c8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java
@@ -0,0 +1,77 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+
+/**
+ * Receives install events and perists them using a {@link EventResultPersister}.
+ */
+public class InstallEventReceiver extends BroadcastReceiver {
+
+    private static final Object sLock = new Object();
+    private static EventResultPersister sReceiver;
+
+    /**
+     * Get the event receiver persisting the results
+     *
+     * @return The event receiver.
+     */
+    @NonNull
+    private static EventResultPersister getReceiver(@NonNull Context context) {
+        synchronized (sLock) {
+            if (sReceiver == null) {
+                sReceiver = new EventResultPersister(
+                    TemporaryFileManager.getInstallStateFile(context));
+            }
+        }
+
+        return sReceiver;
+    }
+
+    /**
+     * Add an observer. If there is already an event for this id, call back inside of this call.
+     *
+     * @param context A context of the current app
+     * @param id The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one.
+     * @param observer The observer to call back.
+     * @return The id for this event
+     */
+    static int addObserver(@NonNull Context context, int id,
+        @NonNull EventResultPersister.EventResultObserver observer)
+        throws EventResultPersister.OutOfIdsException {
+        return getReceiver(context).addObserver(id, observer);
+    }
+
+    /**
+     * Remove a observer.
+     *
+     * @param context A context of the current app
+     * @param id The id the observer was added for
+     */
+    static void removeObserver(@NonNull Context context, int id) {
+        getReceiver(context).removeObserver(id);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getReceiver(context).onEventReceived(context, intent);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
index 03af951..203af44 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
@@ -17,26 +17,42 @@
 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;
@@ -44,22 +60,36 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.lifecycle.MutableLiveData;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.EventResultPersister.OutOfIdsException;
+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;
@@ -75,6 +105,12 @@
     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;
@@ -82,6 +118,7 @@
         mPackageInstaller = mPackageManager.getPackageInstaller();
         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mUserManager = context.getSystemService(UserManager.class);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
     }
 
     /**
@@ -107,6 +144,8 @@
             ? 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) {
@@ -124,13 +163,19 @@
         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 (!isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId)) {
+        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();
         }
 
@@ -216,10 +261,11 @@
 
     public void stageForInstall() {
         Uri uri = mIntent.getData();
-        if (mIsSessionInstall || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
+        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. Setting the mStagingResult as null will signal InstallViewModel to
-            // proceed with user confirmation stage.
+            // for us to stage.
             mStagingResult.setValue(new InstallReady());
             return;
         }
@@ -286,6 +332,10 @@
         }
     }
 
+    public int getStagedSessionId() {
+        return mStagedSessionId;
+    }
+
     private void cleanupStagingSession() {
         if (mStagedSessionId > 0) {
             try {
@@ -337,6 +387,466 @@
         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 (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();
@@ -373,4 +883,29 @@
             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/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
index 82a8c95..fe05237 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
@@ -24,17 +24,25 @@
 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
@@ -199,9 +207,6 @@
      */
     public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
         int sessionId) {
-        if (sessionId == SessionInfo.INVALID_ID) {
-            return false;
-        }
         if (originatingUid == Process.ROOT_UID) {
             return true;
         }
@@ -212,4 +217,246 @@
         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/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/TemporaryFileManager.java
new file mode 100644
index 0000000..3a1c3973
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/TemporaryFileManager.java
@@ -0,0 +1,92 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Manages files of the package installer and resets state during boot.
+ */
+public class TemporaryFileManager extends BroadcastReceiver {
+
+    private static final String LOG_TAG = TemporaryFileManager.class.getSimpleName();
+
+    /**
+     * Create a new file to hold a staged file.
+     *
+     * @param context The context of the caller
+     * @return A new file
+     */
+    @NonNull
+    public static File getStagedFile(@NonNull Context context) throws IOException {
+        return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
+    }
+
+    /**
+     * Get the file used to store the results of installs.
+     *
+     * @param context The context of the caller
+     * @return the file used to store the results of installs
+     */
+    @NonNull
+    public static File getInstallStateFile(@NonNull Context context) {
+        return new File(context.getNoBackupFilesDir(), "install_results.xml");
+    }
+
+    /**
+     * Get the file used to store the results of uninstalls.
+     *
+     * @param context The context of the caller
+     * @return the file used to store the results of uninstalls
+     */
+    @NonNull
+    public static File getUninstallStateFile(@NonNull Context context) {
+        return new File(context.getNoBackupFilesDir(), "uninstall_results.xml");
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        long systemBootTime = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+
+        File[] filesOnBoot = context.getNoBackupFilesDir().listFiles();
+
+        if (filesOnBoot == null) {
+            return;
+        }
+
+        for (int i = 0; i < filesOnBoot.length; i++) {
+            File fileOnBoot = filesOnBoot[i];
+
+            if (systemBootTime > fileOnBoot.lastModified()) {
+                boolean wasDeleted = fileOnBoot.delete();
+                if (!wasDeleted) {
+                    Log.w(LOG_TAG, "Could not delete " + fileOnBoot.getName() + " onBoot");
+                }
+            } else {
+                Log.w(LOG_TAG, fileOnBoot.getName() + " was created before onBoot broadcast was "
+                    + "received");
+            }
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java
new file mode 100644
index 0000000..79e00df
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+
+/**
+ * Receives uninstall events and persists them using a {@link EventResultPersister}.
+ */
+public class UninstallEventReceiver extends BroadcastReceiver {
+    private static final Object sLock = new Object();
+    private static EventResultPersister sReceiver;
+
+    /**
+     * Get the event receiver persisting the results
+     *
+     * @return The event receiver.
+     */
+    @NonNull private static EventResultPersister getReceiver(@NonNull Context context) {
+        synchronized (sLock) {
+            if (sReceiver == null) {
+                sReceiver = new EventResultPersister(
+                        TemporaryFileManager.getUninstallStateFile(context));
+            }
+        }
+
+        return sReceiver;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getReceiver(context).onEventReceived(context, intent);
+    }
+
+    /**
+     * Add an observer. If there is already an event for this id, call back inside of this call.
+     *
+     * @param context  A context of the current app
+     * @param id       The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one.
+     * @param observer The observer to call back.
+     *
+     * @return The id for this event
+     */
+    public static int addObserver(@NonNull Context context, int id,
+            @NonNull EventResultPersister.EventResultObserver observer)
+            throws EventResultPersister.OutOfIdsException {
+        return getReceiver(context).addObserver(id, observer);
+    }
+
+    /**
+     * Remove a observer.
+     *
+     * @param context  A context of the current app
+     * @param id The id the observer was added for
+     */
+    static void removeObserver(@NonNull Context context, int id) {
+        getReceiver(context).removeObserver(id);
+    }
+
+    /**
+     * @param context A context of the current app
+     *
+     * @return A new uninstall id
+     */
+    static int getNewId(@NonNull Context context) throws EventResultPersister.OutOfIdsException {
+        return getReceiver(context).getNewId();
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
new file mode 100644
index 0000000..2e43b75
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
@@ -0,0 +1,714 @@
+/*
+ * 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.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/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
index cc9857d..520b6c5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
@@ -26,6 +26,8 @@
 
     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;
 
@@ -46,13 +48,15 @@
      */
     @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 activityResultCode, int errorDialogType) {
         mAbortReason = reason;
         mMessage = message;
         mIntent = intent;
+        mErrorDialogType = errorDialogType;
         mActivityResultCode = activityResultCode;
     }
 
@@ -70,6 +74,10 @@
         return mIntent;
     }
 
+    public int getErrorDialogType() {
+        return mErrorDialogType;
+    }
+
     public int getActivityResultCode() {
         return mActivityResultCode;
     }
@@ -85,6 +93,7 @@
         private String mMessage = "";
         private Intent mIntent = null;
         private int mActivityResultCode = Activity.RESULT_CANCELED;
+        private int mErrorDialogType;
 
         public Builder(int reason) {
             mAbortReason = reason;
@@ -100,13 +109,19 @@
             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);
+            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
new file mode 100644
index 0000000..67e1690
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
@@ -0,0 +1,69 @@
+/*
+ * 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
new file mode 100644
index 0000000..efd4947
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
@@ -0,0 +1,47 @@
+/*
+ * 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/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
new file mode 100644
index 0000000..da48256
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
@@ -0,0 +1,95 @@
+/*
+ * 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
new file mode 100644
index 0000000..08a7487
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
@@ -0,0 +1,99 @@
+/*
+ * 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
new file mode 100644
index 0000000..9aea6b1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
@@ -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
+ *
+ *      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
new file mode 100644
index 0000000..6ed8883
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
@@ -0,0 +1,119 @@
+/*
+ * 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/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
similarity index 64%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
index 8e55695..0108cb4 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
@@ -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
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.packageinstaller.v2.model.uninstallstagedata;
 
-import org.robolectric.annotation.GraphicsMode;
+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
new file mode 100644
index 0000000..87ca4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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
new file mode 100644
index 0000000..5df6b02
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.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
+ *
+ *      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
new file mode 100644
index 0000000..f5156cb
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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
new file mode 100644
index 0000000..b600149
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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
new file mode 100644
index 0000000..fdb024f
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
@@ -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.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/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
index ba5a0cd..d06b4b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
@@ -16,13 +16,21 @@
 
 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.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.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;
@@ -34,13 +42,25 @@
 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 {
+public class InstallLaunch extends FragmentActivity implements InstallActionListener {
 
     public static final String EXTRA_CALLING_PKG_UID =
             InstallLaunch.class.getPackageName() + ".callingPkgUid";
@@ -48,11 +68,17 @@
             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) {
@@ -61,6 +87,8 @@
         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(
@@ -79,21 +107,68 @@
      * Main controller of the UI. This method shows relevant dialogs based on the install stage
      */
     private void onInstallStageChange(InstallStage installStage) {
-        if (installStage.getStageCode() == InstallStage.STAGE_STAGING) {
-            InstallStagingFragment stagingDialog = new InstallStagingFragment();
-            showDialogInner(stagingDialog);
-            mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
-        } else if (installStage.getStageCode() == 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 ABORT_REASON_INTERNAL_ERROR -> setResult(RESULT_CANCELED, true);
-                case ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
-                default -> setResult(RESULT_CANCELED, true);
+        switch (installStage.getStageCode()) {
+            case InstallStage.STAGE_STAGING -> {
+                InstallStagingFragment stagingDialog = new InstallStagingFragment();
+                showDialogInner(stagingDialog);
+                mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
             }
-        } else {
-            Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
-            showDialogInner(null);
+            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);
+            }
         }
     }
 
@@ -122,7 +197,7 @@
             shouldFinish = false;
             showDialogInner(blockedByPolicyDialog);
         }
-        setResult(RESULT_CANCELED, shouldFinish);
+        setResult(RESULT_CANCELED, null, shouldFinish);
     }
 
     /**
@@ -161,12 +236,119 @@
         }
     }
 
-    public void setResult(int resultCode, boolean shouldFinish) {
-        // TODO: This is incomplete. We need to send RESULT_FIRST_USER, RESULT_OK etc
-        //  for relevant use cases. Investigate when to send what result.
-        super.setResult(resultCode);
+    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/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
index 8e55695..b8a9355 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
@@ -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
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.packageinstaller.v2.ui;
 
-import org.robolectric.annotation.GraphicsMode;
+public interface UninstallActionListener {
+
+    void onPositiveResponse(boolean keepData);
+
+    void 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
new file mode 100644
index 0000000..7638e91
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
@@ -0,0 +1,167 @@
+/*
+ * 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/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
new file mode 100644
index 0000000..6d6fcc9
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -0,0 +1,63 @@
+/*
+ * 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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+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.ui.InstallActionListener;
+
+/**
+ * Dialog to show when the source of apk can not be identified.
+ */
+public class AnonymousSourceFragment extends DialogFragment {
+
+    public static String TAG = AnonymousSourceFragment.class.getSimpleName();
+    private InstallActionListener mInstallActionListener;
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getActivity())
+            .setMessage(R.string.anonymous_source_warning)
+            .setPositiveButton(R.string.anonymous_source_continue,
+                ((dialog, which) -> mInstallActionListener.onPositiveResponse(
+                    InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE)))
+            .setNegativeButton(R.string.cancel,
+                ((dialog, which) -> mInstallActionListener.onNegativeResponse(
+                    InstallStage.STAGE_USER_ACTION_REQUIRED))).create();
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(InstallStage.STAGE_USER_ACTION_REQUIRED);
+    }
+}
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
new file mode 100644
index 0000000..4cdce52
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -0,0 +1,72 @@
+/*
+ * 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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+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.ui.InstallActionListener;
+
+/**
+ * Dialog to show when the installing app is an unknown source and needs AppOp grant to install
+ * other apps.
+ */
+public class ExternalSourcesBlockedFragment extends DialogFragment {
+
+    private final String TAG = ExternalSourcesBlockedFragment.class.getSimpleName();
+    private final InstallUserActionRequired mDialogData;
+    private InstallActionListener mInstallActionListener;
+
+    public ExternalSourcesBlockedFragment(InstallUserActionRequired dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        return new AlertDialog.Builder(requireContext())
+            .setTitle(mDialogData.getAppLabel())
+            .setIcon(mDialogData.getAppIcon())
+            .setMessage(R.string.untrusted_external_source_warning)
+            .setPositiveButton(R.string.external_sources_settings,
+                (dialog, which) -> mInstallActionListener.sendUnknownAppsIntent(
+                    mDialogData.getDialogMessage()))
+            .setNegativeButton(R.string.cancel,
+                (dialog, which) -> mInstallActionListener.onNegativeResponse(
+                    mDialogData.getStageCode()))
+            .create();
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
+    }
+}
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
new file mode 100644
index 0000000..6398aef
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -0,0 +1,95 @@
+/*
+ * 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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.Html;
+import android.view.View;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+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.ui.InstallActionListener;
+
+/**
+ * Dialog to show when the requesting user confirmation for installing an app.
+ */
+public class InstallConfirmationFragment extends DialogFragment {
+
+    public static String TAG = InstallConfirmationFragment.class.getSimpleName();
+
+    @NonNull
+    private final InstallUserActionRequired mDialogData;
+    @NonNull
+    private InstallActionListener mInstallActionListener;
+
+    public InstallConfirmationFragment(@NonNull InstallUserActionRequired dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+
+        AlertDialog dialog = new AlertDialog.Builder(requireContext())
+            .setIcon(mDialogData.getAppIcon())
+            .setTitle(mDialogData.getAppLabel())
+            .setView(dialogView)
+            .setPositiveButton(mDialogData.isAppUpdating() ? R.string.update : R.string.install,
+                (dialogInt, which) -> mInstallActionListener.onPositiveResponse(
+                    InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION))
+            .setNegativeButton(R.string.cancel,
+                (dialogInt, which) -> mInstallActionListener.onNegativeResponse(
+                    mDialogData.getStageCode()))
+
+            .create();
+
+        // TODO: Dynamically change positive button text to update anyway
+        TextView viewToEnable;
+        if (mDialogData.isAppUpdating()) {
+            viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
+            String dialogMessage = mDialogData.getDialogMessage();
+            if (dialogMessage != null) {
+                viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY));
+            }
+        } else {
+            viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
+        }
+        viewToEnable.setVisibility(View.VISIBLE);
+
+        return dialog;
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
+    }
+}
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
new file mode 100644
index 0000000..d45cd76
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.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 com.android.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInstaller;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import androidx.annotation.NonNull;
+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.ui.InstallActionListener;
+
+/**
+ * Dialog to show when the installation failed. Depending on the failure code, an appropriate
+ * message would be shown to the user. This dialog is shown only when the caller does not want the
+ * install result back.
+ */
+public class InstallFailedFragment extends DialogFragment {
+
+    private static final String TAG = InstallFailedFragment.class.getSimpleName();
+    private final InstallFailed mDialogData;
+    private InstallActionListener mInstallActionListener;
+
+    public InstallFailedFragment(InstallFailed dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+        AlertDialog dialog = new AlertDialog.Builder(requireContext())
+            .setTitle(mDialogData.getAppLabel())
+            .setIcon(mDialogData.getAppIcon())
+            .setView(dialogView)
+            .setPositiveButton(R.string.done,
+                (dialogInt, which) -> mInstallActionListener.onNegativeResponse(
+                    mDialogData.getStageCode()))
+            .create();
+        setExplanationFromErrorCode(mDialogData.getStatusCode(), dialogView);
+
+        return dialog;
+    }
+
+    /**
+     * Unhide the appropriate label for the statusCode.
+     *
+     * @param statusCode The status code from the package installer.
+     */
+    private void setExplanationFromErrorCode(int statusCode, View dialogView) {
+        Log.d(TAG, "Installation status code: " + statusCode);
+
+        View viewToEnable;
+        switch (statusCode) {
+            case PackageInstaller.STATUS_FAILURE_BLOCKED:
+                viewToEnable = dialogView.requireViewById(R.id.install_failed_blocked);
+                break;
+            case PackageInstaller.STATUS_FAILURE_CONFLICT:
+                viewToEnable = dialogView.requireViewById(R.id.install_failed_conflict);
+                break;
+            case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
+                viewToEnable = dialogView.requireViewById(R.id.install_failed_incompatible);
+                break;
+            case PackageInstaller.STATUS_FAILURE_INVALID:
+                viewToEnable = dialogView.requireViewById(R.id.install_failed_invalid_apk);
+                break;
+            default:
+                viewToEnable = dialogView.requireViewById(R.id.install_failed);
+                break;
+        }
+
+        viewToEnable.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
+    }
+}
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
new file mode 100644
index 0000000..9f60f96
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
@@ -0,0 +1,64 @@
+/*
+ * 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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
+
+/**
+ * Dialog to show when an install is in progress.
+ */
+public class InstallInstallingFragment extends DialogFragment {
+
+    private final InstallInstalling mDialogData;
+    private AlertDialog mDialog;
+
+    public InstallInstallingFragment(InstallInstalling dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+        mDialog = new AlertDialog.Builder(requireContext())
+            .setTitle(mDialogData.getAppLabel())
+            .setIcon(mDialogData.getAppIcon())
+            .setView(dialogView)
+            .setNegativeButton(R.string.cancel, null)
+            .create();
+
+        dialogView.requireViewById(R.id.installing).setVisibility(View.VISIBLE);
+        this.setCancelable(false);
+
+        return mDialog;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setEnabled(false);
+    }
+}
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
new file mode 100644
index 0000000..ab6a932
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import androidx.annotation.NonNull;
+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.ui.InstallActionListener;
+import java.util.List;
+
+/**
+ * Dialog to show on a successful installation. This dialog is shown only when the caller does not
+ * want the install result back.
+ */
+public class InstallSuccessFragment extends DialogFragment {
+
+    private final InstallSuccess mDialogData;
+    private AlertDialog mDialog;
+    private InstallActionListener mInstallActionListener;
+    private PackageManager mPm;
+
+    public InstallSuccessFragment(InstallSuccess dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+        mPm = context.getPackageManager();
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+        mDialog = new AlertDialog.Builder(requireContext()).setTitle(mDialogData.getAppLabel())
+            .setIcon(mDialogData.getAppIcon()).setView(dialogView).setNegativeButton(R.string.done,
+                (dialog, which) -> mInstallActionListener.onNegativeResponse(
+                    InstallStage.STAGE_SUCCESS))
+            .setPositiveButton(R.string.launch, (dialog, which) -> {
+            }).create();
+
+        dialogView.requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
+
+        return mDialog;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+        boolean enabled = false;
+        if (mDialogData.getResultIntent() != null) {
+            List<ResolveInfo> list = mPm.queryIntentActivities(mDialogData.getResultIntent(), 0);
+            if (list.size() > 0) {
+                enabled = true;
+            }
+        }
+        if (enabled) {
+            launchButton.setOnClickListener(view -> {
+                mInstallActionListener.openInstalledApp(mDialogData.getResultIntent());
+            });
+        } else {
+            launchButton.setEnabled(false);
+        }
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
+    }
+}
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 dce0b9a..47fd67f 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
@@ -16,36 +16,47 @@
 
 package com.android.packageinstaller.v2.ui.fragments;
 
-import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
 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.ui.InstallActionListener;
 
 public class SimpleErrorFragment extends DialogFragment {
 
     private static final String TAG = SimpleErrorFragment.class.getSimpleName();
     private final int mMessageResId;
+    private InstallActionListener mInstallActionListener;
 
     public SimpleErrorFragment(int messageResId) {
         mMessageResId = messageResId;
     }
 
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         return new AlertDialog.Builder(getActivity())
             .setMessage(mMessageResId)
-            .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
+            .setPositiveButton(R.string.ok,
+                (dialog, which) ->
+                    mInstallActionListener.onNegativeResponse(InstallStage.STAGE_ABORTED))
             .create();
     }
 
     @Override
     public void onCancel(DialogInterface dialog) {
-        getActivity().setResult(Activity.RESULT_CANCELED);
-        getActivity().finish();
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(InstallStage.STAGE_ABORTED);
     }
 }
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
new file mode 100644
index 0000000..1b0885e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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.fragments;
+
+import static android.text.format.Formatter.formatFileSize;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+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.ui.UninstallActionListener;
+
+/**
+ * Dialog to show while requesting user confirmation for uninstalling an app.
+ */
+public class UninstallConfirmationFragment extends DialogFragment {
+
+    private final UninstallUserActionRequired mDialogData;
+    private UninstallActionListener mUninstallActionListener;
+
+    private CheckBox mKeepData;
+
+    public UninstallConfirmationFragment(UninstallUserActionRequired dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mUninstallActionListener = (UninstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
+            .setTitle(mDialogData.getTitle())
+            .setPositiveButton(R.string.ok,
+                (dialogInt, which) -> mUninstallActionListener.onPositiveResponse(
+                    mKeepData != null && mKeepData.isChecked()))
+            .setNegativeButton(R.string.cancel,
+                (dialogInt, which) -> mUninstallActionListener.onNegativeResponse());
+
+        long appDataSize = mDialogData.getAppDataSize();
+        if (appDataSize == 0) {
+            builder.setMessage(mDialogData.getMessage());
+        } else {
+            View dialogView = getLayoutInflater().inflate(R.layout.uninstall_content_view, null);
+
+            ((TextView) dialogView.requireViewById(R.id.message)).setText(mDialogData.getMessage());
+            mKeepData = dialogView.requireViewById(R.id.keepData);
+            mKeepData.setVisibility(View.VISIBLE);
+            mKeepData.setText(getString(R.string.uninstall_keep_data,
+                formatFileSize(getContext(), appDataSize)));
+
+            builder.setView(dialogView);
+        }
+        return builder.create();
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mUninstallActionListener.onNegativeResponse();
+    }
+}
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
new file mode 100644
index 0000000..305daba
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -0,0 +1,68 @@
+/*
+ * 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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+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.ui.UninstallActionListener;
+
+/**
+ * Dialog to show when an app cannot be uninstalled
+ */
+public class UninstallErrorFragment extends DialogFragment {
+
+    private final UninstallAborted mDialogData;
+    private UninstallActionListener mUninstallActionListener;
+
+    public UninstallErrorFragment(UninstallAborted dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mUninstallActionListener = (UninstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
+            .setMessage(mDialogData.getDialogTextResource())
+            .setNegativeButton(R.string.ok,
+                (dialogInt, which) -> mUninstallActionListener.onNegativeResponse());
+
+        if (mDialogData.getDialogTitleResource() != 0) {
+            builder.setTitle(mDialogData.getDialogTitleResource());
+        }
+        return builder.create();
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mUninstallActionListener.onNegativeResponse();
+    }
+}
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
new file mode 100644
index 0000000..23cc421
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
+
+/**
+ * Dialog to show that the app is uninstalling.
+ */
+public class UninstallUninstallingFragment extends DialogFragment {
+
+    UninstallUninstalling mDialogData;
+
+    public UninstallUninstallingFragment(UninstallUninstalling dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
+            .setCancelable(false);
+        if (mDialogData.isCloneUser()) {
+            builder.setTitle(requireContext().getString(R.string.uninstalling_cloned_app,
+                mDialogData.getAppLabel()));
+        } else {
+            builder.setTitle(requireContext().getString(R.string.uninstalling_app,
+                mDialogData.getAppLabel()));
+        }
+        Dialog dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+
+        return dialog;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
index 42b3023..04a0622 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
@@ -58,7 +58,7 @@
                 if (installStage.getStageCode() != InstallStage.STAGE_READY) {
                     mCurrentInstallStage.setValue(installStage);
                 } else {
-                    // Proceed with user confirmation here.
+                    checkIfAllowedAndInitiateInstall();
                 }
             });
         }
@@ -67,4 +67,39 @@
     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/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
new file mode 100644
index 0000000..3f7bce8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
@@ -0,0 +1,69 @@
+/*
+ * 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/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
new file mode 100644
index 0000000..cd9845e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 4f2719f..e42ef39 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -20,9 +20,9 @@
     <string name="more_options_button" msgid="2243228396432556771">"ज़्यादा विकल्प"</string>
     <string name="label_destination" msgid="9132510997381599275">"गंतव्य"</string>
     <string name="label_copies" msgid="3634531042822968308">"प्रतियां"</string>
-    <string name="label_copies_summary" msgid="3861966063536529540">"प्रतियां:"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"कॉपी:"</string>
     <string name="label_paper_size" msgid="908654383827777759">"काग़ज़ का आकार"</string>
-    <string name="label_paper_size_summary" msgid="5668204981332138168">"काग़ज़ का आकार:"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"काग़ज़ का साइज़:"</string>
     <string name="label_color" msgid="1108690305218188969">"रंग"</string>
     <string name="label_duplex" msgid="5370037254347072243">"दो-तरफ़ा"</string>
     <string name="label_orientation" msgid="2853142581990496477">"स्क्रीन की दिशा"</string>
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 4871ef3..010a6ce 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -17,7 +17,6 @@
     static_libs: [
         "androidx.preference_preference",
         "SettingsLibSettingsTheme",
-        "SettingsLibUtils",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
index 4b3acbf..e70114f 100644
--- a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
+++ b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
@@ -17,5 +17,5 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.widget.mainswitch">
-
+    <uses-sdk android:minSdkVersion="21" />
 </manifest>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 2b5fcd8..e6f61a8 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -30,7 +32,6 @@
 
 import androidx.annotation.ColorInt;
 
-import com.android.settingslib.utils.BuildCompatUtils;
 import com.android.settingslib.widget.mainswitch.R;
 
 import java.util.ArrayList;
@@ -72,11 +73,18 @@
 
         LayoutInflater.from(context).inflate(R.layout.settingslib_main_switch_bar, this);
 
-        if (!BuildCompatUtils.isAtLeastS()) {
-            final TypedArray a = context.obtainStyledAttributes(
-                    new int[]{android.R.attr.colorAccent});
-            mBackgroundActivatedColor = a.getColor(0, 0);
-            mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
+        if (Build.VERSION.SDK_INT < VERSION_CODES.S) {
+            TypedArray a;
+            if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
+                a = context.obtainStyledAttributes(
+                        new int[]{android.R.attr.colorAccent});
+                mBackgroundActivatedColor = a.getColor(0, 0);
+                mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
+            } else {
+                a = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
+                mBackgroundActivatedColor = a.getColor(0, 0);
+                mBackgroundColor = a.getColor(0, 0);
+            }
             a.recycle();
         }
 
@@ -148,7 +156,7 @@
      * Set icon space reserved for title
      */
     public void setIconSpaceReserved(boolean iconSpaceReserved) {
-        if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
+        if (mTextView != null && (Build.VERSION.SDK_INT < VERSION_CODES.S)) {
             LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
             int iconSpace = getContext().getResources().getDimensionPixelSize(
                     R.dimen.settingslib_switchbar_subsettings_margin_start);
@@ -207,7 +215,7 @@
         mTextView.setEnabled(enabled);
         mSwitch.setEnabled(enabled);
 
-        if (BuildCompatUtils.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= VERSION_CODES.S) {
             mFrameView.setEnabled(enabled);
             mFrameView.setActivated(isChecked());
         }
@@ -222,7 +230,7 @@
     }
 
     private void setBackground(boolean isChecked) {
-        if (!BuildCompatUtils.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT < VERSION_CODES.S) {
             setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor);
         } else {
             mFrameView.setActivated(isChecked);
diff --git a/packages/SettingsLib/ProfileSelector/res/values/styles.xml b/packages/SettingsLib/ProfileSelector/res/values/styles.xml
index 0b703c9..365dcb2 100644
--- a/packages/SettingsLib/ProfileSelector/res/values/styles.xml
+++ b/packages/SettingsLib/ProfileSelector/res/values/styles.xml
@@ -37,5 +37,6 @@
         <item name="tabIndicatorAnimationDuration">0</item>
         <item name="tabTextAppearance">@style/SettingsLibTabsTextAppearance</item>
         <item name="tabTextColor">@color/settingslib_tabs_text_color</item>
+        <item name="tabRippleColor">@android:color/transparent</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index b1e1585..90c7d46 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -49,6 +49,7 @@
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
+import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
 
@@ -100,6 +101,7 @@
                 SettingsTextFieldPasswordPageProvider,
                 SearchScaffoldPageProvider,
                 CardPageProvider,
+                CopyablePageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index f52ceec..1d897f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -44,6 +44,7 @@
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
+import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
 
@@ -71,6 +72,7 @@
             AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
new file mode 100644
index 0000000..f897d8c
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.ui
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CopyableBody
+
+private const val TITLE = "Sample Copyable"
+
+object CopyablePageProvider : SettingsPageProvider {
+    override val name = "Copyable"
+
+    private val owner = createSettingsPage()
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+            .setSearchDataFn { EntrySearchData(title = TITLE) }
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(title = TITLE) {
+            Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+                CopyableBody(body = "Copyable body")
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 905640f..b40e911 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.1.3"
+agp = "8.1.4"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 7c135a0..63efaf5 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
index 7b438c4..3ac1d0f 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
index ac64619..105d1a1 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
index 5506c8c..a038779 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
index f4b9063..a042ffb 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
index fc60c0f..ae0abdde 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index 81a181f..ae11f81 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
index a620040..9bc2b5d 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
index 0b40aa8..fc845a6 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
index cfe8587..03db688 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
index 3632755..235df22 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
index 7e5b602..9044208 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 8a0da31..8333e68 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
index 236a1a0..fcfd1d8 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index 72b7954..693c592 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
index 36486c4..1345c37 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
index fedce44..d0d014e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index 3b389d7..5bd1144 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
index 83d7549..23fdc01 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
@@ -12,4 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-sdk=NEWEST_SDK
\ No newline at end of file
+sdk=NEWEST_SDK
+graphicsMode=NATIVE
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 1cbdc33..ae85675 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -94,3 +94,18 @@
         screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
     }
 }
+
+/** Create a [SettingsScreenshotTestRule] for settings screenshot tests. */
+fun settingsScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+): SettingsScreenshotTestRule {
+    val assetPath = if (Build.FINGERPRINT.contains("robolectric")) {
+        "frameworks/base/packages/SettingsLib/Spa/screenshot/robotests/assets"
+    } else {
+        "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+    }
+    return SettingsScreenshotTestRule(
+        emulationSpec,
+        assetPath
+    )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
index 2cb6044..8f762f6 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
@@ -20,7 +20,7 @@
 import androidx.compose.material.icons.automirrored.outlined.Launch
 import androidx.compose.material.icons.outlined.Delete
 import androidx.compose.material.icons.outlined.WarningAmber
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.button.ActionButton
 import com.android.settingslib.spa.widget.button.ActionButtons
 import org.junit.Rule
@@ -42,9 +42,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index 7ef9f10..d766425 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.screenshot.widget.chart
 
 import androidx.compose.material3.MaterialTheme
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.chart.BarChart
 import com.android.settingslib.spa.widget.chart.BarChartData
 import com.android.settingslib.spa.widget.chart.BarChartModel
@@ -41,9 +41,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
index 3790164..495bcbc 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spa.screenshot.widget.chart
 
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.chart.LineChart
 import com.android.settingslib.spa.widget.chart.LineChartData
 import com.android.settingslib.spa.widget.chart.LineChartModel
@@ -41,9 +41,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
index 3c3cc85..ee61aad 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spa.screenshot.widget.chart
 
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.chart.PieChart
 import com.android.settingslib.spa.widget.chart.PieChartData
 import com.android.settingslib.spa.widget.chart.PieChartModel
@@ -39,9 +39,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
deleted file mode 100644
index afe3f07..0000000
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.chart;
-
-import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
index 616b225..94d032c 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.screenshot.widget.illustration
 
 import com.android.settingslib.spa.screenshot.R
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.illustration.Illustration
 import com.android.settingslib.spa.widget.illustration.IllustrationModel
 import com.android.settingslib.spa.widget.illustration.ResourceType
@@ -40,9 +40,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
deleted file mode 100644
index 0089c2e..0000000
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.illustration;
-
-import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
index 8dd4ce7..2a01d84 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.screenshot.widget.preference
 
 import androidx.compose.foundation.layout.Column
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.MainSwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import org.junit.Rule
@@ -39,9 +39,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
index 1e1a785..4d8650e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
@@ -21,7 +21,7 @@
 import androidx.compose.material.icons.outlined.Autorenew
 import androidx.compose.material.icons.outlined.DisabledByDefault
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.ui.SettingsIcon
@@ -48,9 +48,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
index d1878a74..3983cc0 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
@@ -21,7 +21,7 @@
 import androidx.compose.material.icons.outlined.Delete
 import androidx.compose.material.icons.outlined.SystemUpdate
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.ProgressBarPreference
 import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
 import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
@@ -45,9 +45,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
index c9f098b..3a96a70 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.AccessAlarm
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.SliderPreference
 import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
 import org.junit.Rule
@@ -41,9 +41,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
index eca40fb..4a8064a 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
@@ -20,7 +20,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spa.widget.ui.SettingsIcon
@@ -43,9 +43,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
index f81a59f..91b7b24 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.foundation.layout.Column
 import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
 import org.junit.Rule
@@ -40,9 +40,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
deleted file mode 100644
index fd6a5dd..0000000
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.preference;
-
-import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
index 98a4288..6ba010f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spa.screenshot.widget.ui
 
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.ui.Footer
 import org.junit.Rule
 import org.junit.Test
@@ -37,9 +37,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
index 5417095..320b207 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spa.screenshot.widget.ui
 
-import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
+import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spa.widget.ui.SpinnerOption
 import org.junit.Rule
@@ -38,9 +38,8 @@
 
     @get:Rule
     val screenshotRule =
-        SettingsScreenshotTestRule(
+        settingsScreenshotTestRule(
             emulationSpec,
-            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
         )
 
     @Test
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
new file mode 100644
index 0000000..3991f26
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.platform.LocalLifecycleOwner
+
+/**
+ * An effect for detecting presses of the system back button, and the back event will not be
+ * consumed by this effect.
+ *
+ * Calling this in your composable adds the given lambda to the [OnBackPressedDispatcher] of the
+ * [LocalOnBackPressedDispatcherOwner].
+ *
+ * @param onBack the action invoked by pressing the system back
+ */
+@Composable
+fun OnBackEffect(onBack: () -> Unit) {
+    val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
+        "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
+    }.onBackPressedDispatcher
+
+    // Safely update the current `onBack` lambda when a new one is provided
+    val currentOnBack by rememberUpdatedState(onBack)
+    // Remember in Composition a back callback that calls the `onBack` lambda
+    val backCallback = remember {
+        object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() {
+                remove()
+                currentOnBack()
+                backDispatcher.onBackPressed()
+            }
+        }
+    }
+    val lifecycleOwner = LocalLifecycleOwner.current
+    DisposableEffect(lifecycleOwner, backDispatcher) {
+        // Add callback to the backDispatcher
+        backDispatcher.addCallback(lifecycleOwner, backCallback)
+        // When the effect leaves the Composition, remove the callback
+        onDispose {
+            backCallback.remove()
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt
new file mode 100644
index 0000000..930d0a1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MenuDefaults
+import androidx.compose.material3.Text
+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.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.unit.DpOffset
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun CopyableBody(body: String) {
+    var expanded by remember { mutableStateOf(false) }
+    var dpOffset by remember { mutableStateOf(DpOffset.Unspecified) }
+
+    Box(modifier = Modifier
+        .fillMaxWidth()
+        .pointerInput(Unit) {
+            detectTapGestures(
+                onLongPress = {
+                    dpOffset = DpOffset(it.x.toDp(), it.y.toDp())
+                    expanded = true
+                },
+            )
+        }
+    ) {
+        SettingsBody(body)
+
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false },
+            offset = dpOffset,
+        ) {
+            DropdownMenuTitle(body)
+            DropdownMenuCopy(body) { expanded = false }
+        }
+    }
+}
+
+@Composable
+private fun DropdownMenuTitle(text: String) {
+    Text(
+        text = text,
+        modifier = Modifier
+            .padding(MenuDefaults.DropdownMenuItemContentPadding)
+            .padding(
+                top = SettingsDimension.itemPaddingAround,
+                bottom = SettingsDimension.buttonPaddingVertical,
+            ),
+        color = SettingsTheme.colorScheme.categoryTitle,
+        style = MaterialTheme.typography.labelMedium,
+    )
+}
+
+@Composable
+private fun DropdownMenuCopy(body: String, onCopy: () -> Unit) {
+    val clipboardManager = LocalClipboardManager.current
+    DropdownMenuItem(
+        text = {
+            Text(
+                text = stringResource(android.R.string.copy),
+                color = MaterialTheme.colorScheme.onSurface,
+                style = MaterialTheme.typography.bodyLarge,
+            )
+        },
+        onClick = {
+            onCopy()
+            clipboardManager.setText(AnnotatedString(body))
+        }
+    )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt
new file mode 100644
index 0000000..5881686
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OnBackEffectTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var onBackEffectCalled = false
+
+    @Test
+    fun onBackEffect() {
+        composeTestRule.setContent {
+            TestNavHost {
+                val navController = LocalNavController.current
+                LaunchedEffect(Unit) {
+                    navController.navigate(ROUTE_B)
+                    delay(100)
+                    navController.navigateBack()
+                }
+            }
+        }
+
+        composeTestRule.waitUntilExists(hasText(ROUTE_A))
+        assertThat(onBackEffectCalled).isTrue()
+    }
+
+    @Composable
+    private fun TestNavHost(content: @Composable () -> Unit) {
+        val navController = rememberNavController()
+        CompositionLocalProvider(navController.localNavController()) {
+            NavHost(navController, ROUTE_A) {
+                composable(route = ROUTE_A) { Text(ROUTE_A) }
+                composable(route = ROUTE_B) {
+                    Text(ROUTE_B)
+
+                    OnBackEffect {
+                        onBackEffectCalled = true
+                    }
+                }
+            }
+            content()
+        }
+    }
+
+    private companion object {
+        const val ROUTE_A = "RouteA"
+        const val ROUTE_B = "RouteB"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
new file mode 100644
index 0000000..71072a5
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CopyableBodyTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun text_isDisplayed() {
+        composeTestRule.setContent {
+            CopyableBody(TEXT)
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun onLongPress_contextMenuDisplayed() {
+        composeTestRule.setContent {
+            CopyableBody(TEXT)
+        }
+
+        composeTestRule.onNodeWithText(TEXT).performTouchInput {
+            longClick()
+        }
+
+        composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).assertIsDisplayed()
+    }
+
+    @Test
+    fun onCopy_saveToClipboard() {
+        val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
+        clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+        composeTestRule.setContent {
+            CopyableBody(TEXT)
+        }
+
+        composeTestRule.onNodeWithText(TEXT).performTouchInput {
+            longClick()
+        }
+        composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
+
+        assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index 3525037..5baf7be 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -16,20 +16,22 @@
 
 package com.android.settingslib.spaprivileged.model.enterprise
 
-import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
 import android.content.Context
 import android.content.pm.UserInfo
 import com.android.settingslib.R
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
 
-class EnterpriseRepository(private val context: Context) {
-    private val resources by lazy {
-        checkNotNull(context.getSystemService(DevicePolicyManager::class.java)).resources
-    }
+interface IEnterpriseRepository {
+    fun getEnterpriseString(updatableStringId: String, resId: Int): String
+}
 
-    fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+class EnterpriseRepository(private val context: Context) : IEnterpriseRepository {
+    private val resources by lazy { context.devicePolicyManager.resources }
+
+    override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
         checkNotNull(resources.getString(updatableStringId) { context.getString(resId) })
 
     fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
new file mode 100644
index 0000000..3acc9ad
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.enterprise
+
+import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.content.Context
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.widget.restricted.R
+
+sealed interface RestrictedMode
+
+data object NoRestricted : RestrictedMode
+
+data object BaseUserRestricted : RestrictedMode
+
+interface BlockedByAdmin : RestrictedMode {
+    fun getSummary(checked: Boolean?): String
+    fun sendShowAdminSupportDetailsIntent()
+}
+
+internal data class BlockedByAdminImpl(
+    private val context: Context,
+    private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin,
+    private val enterpriseRepository: IEnterpriseRepository = EnterpriseRepository(context),
+) : BlockedByAdmin {
+    override fun getSummary(checked: Boolean?) = when (checked) {
+        true -> enterpriseRepository.getEnterpriseString(
+            updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.enabled_by_admin,
+        )
+
+        false -> enterpriseRepository.getEnterpriseString(
+            updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.disabled_by_admin,
+        )
+
+        else -> ""
+    }
+
+    override fun sendShowAdminSupportDetailsIntent() {
+        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 09cb98e..550966b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.model.enterprise
 
-import android.app.admin.DevicePolicyResources.Strings.Settings
 import android.content.Context
 import android.os.UserHandle
 import android.os.UserManager
@@ -25,55 +24,16 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.RestrictedLockUtils
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.settingslib.RestrictedLockUtilsInternal
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
-import com.android.settingslib.widget.restricted.R
 
 data class Restrictions(
     val userId: Int = UserHandle.myUserId(),
     val keys: List<String>,
 )
 
-sealed interface RestrictedMode
-
-data object NoRestricted : RestrictedMode
-
-data object BaseUserRestricted : RestrictedMode
-
-interface BlockedByAdmin : RestrictedMode {
-    fun getSummary(checked: Boolean?): String
-    fun sendShowAdminSupportDetailsIntent()
-}
-
-private data class BlockedByAdminImpl(
-    private val context: Context,
-    private val enforcedAdmin: EnforcedAdmin,
-) : BlockedByAdmin {
-    private val enterpriseRepository by lazy { EnterpriseRepository(context) }
-
-    override fun getSummary(checked: Boolean?) = when (checked) {
-        true -> enterpriseRepository.getEnterpriseString(
-            updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
-            resId = R.string.enabled_by_admin,
-        )
-
-        false -> enterpriseRepository.getEnterpriseString(
-            updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
-            resId = R.string.disabled_by_admin,
-        )
-
-        else -> ""
-    }
-
-    override fun sendShowAdminSupportDetailsIntent() {
-        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
-    }
-}
-
 interface RestrictionsProvider {
     @Composable
     fun restrictedModeState(): State<RestrictedMode?>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index fc10a27..45295b0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -36,10 +36,10 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
 import com.android.settingslib.development.DevelopmentSettingsEnabler
 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.CopyableBody
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 import com.android.settingslib.spaprivileged.R
@@ -71,26 +71,38 @@
     @Composable
     private fun InstallType(app: ApplicationInfo) {
         if (!app.isInstantApp) return
-        Spacer(modifier = Modifier.height(4.dp))
-        SettingsBody(stringResource(com.android.settingslib.widget.preference.app.R.string.install_type_instant))
+        Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall))
+        SettingsBody(
+            stringResource(
+                com.android.settingslib.widget.preference.app.R.string.install_type_instant
+            )
+        )
     }
 
     @Composable
     private fun AppVersion() {
-        if (packageInfo.versionName == null) return
-        Spacer(modifier = Modifier.height(4.dp))
-        SettingsBody(packageInfo.versionNameBidiWrapped)
+        val versionName = packageInfo.versionNameBidiWrapped ?: return
+        Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall))
+        SettingsBody(versionName)
     }
 
     @Composable
     fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
-        if (packageInfo.versionName == null) return
+        val context = LocalContext.current
+        val footer = remember(showPackageName) {
+            val list = mutableListOf<String>()
+            packageInfo.versionNameBidiWrapped?.let {
+                list += context.getString(R.string.version_text, it)
+            }
+            if (showPackageName) {
+                list += packageInfo.packageName
+            }
+            list.joinToString(separator = System.lineSeparator())
+        }
+        if (footer.isBlank()) return
         HorizontalDivider()
         Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
-            SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped))
-            if (showPackageName) {
-                SettingsBody(packageInfo.packageName)
-            }
+            CopyableBody(footer)
         }
     }
 
@@ -104,7 +116,7 @@
 
     private companion object {
         /** Wrapped the version name, so its directionality still keep same when RTL. */
-        val PackageInfo.versionNameBidiWrapped: String
+        val PackageInfo.versionNameBidiWrapped: String?
             get() = BidiFormatter.getInstance().unicodeWrap(versionName)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 7bd7fbe..06b3eab 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -16,9 +16,7 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_DEFAULT
-import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
@@ -64,7 +62,7 @@
      */
     open val permissionHasAppOpFlag: Boolean = true
 
-    open val modeForNotAllowed: Int = MODE_ERRORED
+    open val modeForNotAllowed: Int = AppOpsManager.MODE_ERRORED
 
     /**
      * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
@@ -130,27 +128,14 @@
     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
         recordListFlow.filterItem(::isChangeable)
 
-    /**
-     * Defining the default behavior as permissible as long as the package requested this permission
-     * (This means pre-M gets approval during install time; M apps gets approval during runtime).
-     */
     @Composable
-    override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? {
-        if (record.hasRequestBroaderPermission) {
-            // Broader permission trumps the specific permission.
-            return { true }
-        }
-
-        val mode = record.appOpsController.mode.observeAsState()
-        return {
-            when (mode.value) {
-                null -> null
-                MODE_ALLOWED -> true
-                MODE_DEFAULT -> with(packageManagers) { record.app.hasGrantPermission(permission) }
-                else -> false
-            }
-        }
-    }
+    override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? =
+        isAllowed(
+            record = record,
+            appOpsController = record.appOpsController,
+            permission = permission,
+            packageManagers = packageManagers,
+        )
 
     override fun isChangeable(record: AppOpPermissionRecord) =
         record.hasRequestPermission &&
@@ -161,3 +146,33 @@
         record.appOpsController.setAllowed(newAllowed)
     }
 }
+
+/**
+ * Defining the default behavior as permissible as long as the package requested this permission
+ * (This means pre-M gets approval during install time; M apps gets approval during runtime).
+ */
+@Composable
+internal fun isAllowed(
+    record: AppOpPermissionRecord,
+    appOpsController: IAppOpsController,
+    permission: String,
+    packageManagers: IPackageManagers = PackageManagers,
+): () -> Boolean? {
+    if (record.hasRequestBroaderPermission) {
+        // Broader permission trumps the specific permission.
+        return { true }
+    }
+
+    val mode = appOpsController.mode.observeAsState()
+    return {
+        when (mode.value) {
+            null -> null
+            AppOpsManager.MODE_ALLOWED -> true
+            AppOpsManager.MODE_DEFAULT -> {
+                with(packageManagers) { record.app.hasGrantPermission(permission) }
+            }
+
+            else -> false
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 3380b7d..5655436 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -16,19 +16,16 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.os.Bundle
 import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.core.os.bundleOf
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.common.SettingsEntry
@@ -49,6 +46,8 @@
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 internal class TogglePermissionAppInfoPageProvider(
     private val appListTemplate: TogglePermissionAppListTemplate,
@@ -132,7 +131,7 @@
 
 @VisibleForTesting
 @Composable
-internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfoPage(
+internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionAppInfoPage(
     packageName: String,
     userId: Int,
     packageManagers: IPackageManagers = PackageManagers,
@@ -145,40 +144,34 @@
         footerContent = { AnnotatedText(footerResId) },
         packageManagers = packageManagers,
     ) {
-        val model = createSwitchModel(checkNotNull(applicationInfo))
+        val app = applicationInfo ?: return@AppInfoPage
+        val record = rememberRecord(app).value ?: return@AppInfoPage
+        val isAllowed = isAllowed(record)
+        val isChangeable by rememberIsChangeable(record)
+        val switchModel = object : SwitchPreferenceModel {
+            override val title = stringResource(switchTitleResId)
+            override val checked = isAllowed
+            override val changeable = { isChangeable }
+            override val onCheckedChange: (Boolean) -> Unit = { setAllowed(record, it) }
+        }
         val restrictions = Restrictions(userId, switchRestrictionKeys)
-        RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory)
+        RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
     }
 }
 
 @Composable
-private fun <T : AppRecord> TogglePermissionAppListModel<T>.createSwitchModel(
-    app: ApplicationInfo,
-): TogglePermissionSwitchModel<T> {
-    val context = LocalContext.current
-    val record = remember(app) { transformItem(app) }
-    val isAllowed = isAllowed(record)
-    return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) }
-        .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } }
-}
+private fun <T : AppRecord> TogglePermissionAppListModel<T>.rememberRecord(app: ApplicationInfo) =
+    remember(app) {
+        flow {
+            emit(transformItem(app))
+        }.flowOn(Dispatchers.Default)
+    }.collectAsStateWithLifecycle(initialValue = null)
 
-private class TogglePermissionSwitchModel<T : AppRecord>(
-    context: Context,
-    private val listModel: TogglePermissionAppListModel<T>,
-    private val record: T,
-    isAllowed: () -> Boolean?,
-) : SwitchPreferenceModel {
-    private var appChangeable by mutableStateOf(true)
 
-    override val title: String = context.getString(listModel.switchTitleResId)
-    override val checked = isAllowed
-    override val changeable = { appChangeable }
-
-    fun initState() {
-        appChangeable = listModel.isChangeable(record)
-    }
-
-    override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
-        listModel.setAllowed(record, newChecked)
-    }
-}
+@Composable
+private fun <T : AppRecord> TogglePermissionAppListModel<T>.rememberIsChangeable(record: T) =
+    remember(record) {
+        flow {
+            emit(isChangeable(record))
+        }.flowOn(Dispatchers.Default)
+    }.collectAsStateWithLifecycle(initialValue = false)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 785f779..36c91f4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,7 +45,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
-import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel
 import kotlinx.coroutines.flow.Flow
 
 private const val ENTRY_NAME = "AppList"
@@ -157,7 +157,7 @@
         }
         val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
-        return RestrictedSwitchPreference.getSummary(
+        return RestrictedSwitchPreferenceModel.getSummary(
             context = context,
             restrictedModeSupplier = { restrictedMode },
             summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index 223e99e..5679694 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -72,7 +72,8 @@
 
 private fun UserManager.showInSettings(userInfo: UserInfo): Int {
     val userProperties = getUserProperties(userInfo.userHandle)
-    return if (userInfo.isQuietModeEnabled && userProperties.hideInSettingsInQuietMode) {
+    return if (userInfo.isQuietModeEnabled && userProperties.showInQuietMode
+            == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) {
         UserProperties.SHOW_IN_SETTINGS_NO
     } else {
         userProperties.showInSettings
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt
new file mode 100644
index 0000000..125f636
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.MainSwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedMainSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
+    RestrictedMainSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedMainSwitchPreference(
+    model: SwitchPreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+    if (restrictions.keys.isEmpty()) {
+        MainSwitchPreference(model)
+        return
+    }
+    restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) {
+        MainSwitchPreference(it)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e41976f..d5c5574 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -16,29 +16,14 @@
 
 package com.android.settingslib.spaprivileged.template.preference
 
-import android.content.Context
 import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.state.ToggleableState
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
-import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
-import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
-import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
-import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
 
 @Composable
 fun RestrictedSwitchPreference(
@@ -59,91 +44,7 @@
         SwitchPreference(model)
         return
     }
-    val context = LocalContext.current
-    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
-    val restrictedSwitchModel = remember(restrictedMode) {
-        RestrictedSwitchPreferenceModel(context, model, restrictedMode)
-    }
-    restrictedSwitchModel.RestrictionWrapper {
-        SwitchPreference(restrictedSwitchModel)
-    }
-}
-
-internal object RestrictedSwitchPreference {
-    fun getSummary(
-        context: Context,
-        restrictedModeSupplier: () -> RestrictedMode?,
-        summaryIfNoRestricted: () -> String,
-        checked: () -> Boolean?,
-    ): () -> String = {
-        when (val restrictedMode = restrictedModeSupplier()) {
-            is NoRestricted -> summaryIfNoRestricted()
-            is BaseUserRestricted -> context.getString(com.android.settingslib.R.string.disabled)
-            is BlockedByAdmin -> restrictedMode.getSummary(checked())
-            null -> context.getPlaceholder()
-        }
-    }
-}
-
-private class RestrictedSwitchPreferenceModel(
-    context: Context,
-    model: SwitchPreferenceModel,
-    private val restrictedMode: RestrictedMode?,
-) : SwitchPreferenceModel {
-    override val title = model.title
-
-    override val summary = RestrictedSwitchPreference.getSummary(
-        context = context,
-        restrictedModeSupplier = { restrictedMode },
-        summaryIfNoRestricted = model.summary,
-        checked = model.checked,
-    )
-
-    override val checked = when (restrictedMode) {
-        null -> ({ null })
-        is NoRestricted -> model.checked
-        is BaseUserRestricted -> ({ false })
-        is BlockedByAdmin -> model.checked
-    }
-
-    override val changeable = when (restrictedMode) {
-        null -> ({ false })
-        is NoRestricted -> model.changeable
-        is BaseUserRestricted -> ({ false })
-        is BlockedByAdmin -> ({ false })
-    }
-
-    override val onCheckedChange = when (restrictedMode) {
-        null -> null
-        is NoRestricted -> model.onCheckedChange
-        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
-        // is false so this will not be called.
-        is BaseUserRestricted -> model.onCheckedChange
-        // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
-        is BlockedByAdmin -> null
-    }
-
-    @Composable
-    fun RestrictionWrapper(content: @Composable () -> Unit) {
-        if (restrictedMode !is BlockedByAdmin) {
-            content()
-            return
-        }
-        Box(
-            Modifier
-                .clickable(
-                    role = Role.Switch,
-                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
-                )
-                .semantics {
-                    this.toggleableState = ToggleableState(checked())
-                },
-        ) { content() }
-    }
-
-    private fun ToggleableState(value: Boolean?) = when (value) {
-        true -> ToggleableState.On
-        false -> ToggleableState.Off
-        null -> ToggleableState.Indeterminate
+    restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) {
+        SwitchPreference(it)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
new file mode 100644
index 0000000..fa44ecb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import android.content.Context
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+internal class RestrictedSwitchPreferenceModel(
+    context: Context,
+    model: SwitchPreferenceModel,
+    private val restrictedMode: RestrictedMode?,
+) : SwitchPreferenceModel {
+    override val title = model.title
+
+    override val summary = getSummary(
+        context = context,
+        restrictedModeSupplier = { restrictedMode },
+        summaryIfNoRestricted = model.summary,
+        checked = model.checked,
+    )
+
+    override val checked = when (restrictedMode) {
+        null -> ({ null })
+        is NoRestricted -> model.checked
+        is BaseUserRestricted -> ({ false })
+        is BlockedByAdmin -> model.checked
+    }
+
+    override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+
+    override val onCheckedChange = when (restrictedMode) {
+        null -> null
+        is NoRestricted -> model.onCheckedChange
+        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+        // is false so this will not be called.
+        is BaseUserRestricted -> model.onCheckedChange
+        // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
+        is BlockedByAdmin -> null
+    }
+
+    @Composable
+    fun RestrictionWrapper(content: @Composable () -> Unit) {
+        if (restrictedMode !is BlockedByAdmin) {
+            content()
+            return
+        }
+        Box(
+            Modifier
+                .clickable(
+                    role = Role.Switch,
+                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                )
+                .semantics {
+                    this.toggleableState = ToggleableState(checked())
+                },
+        ) { content() }
+    }
+
+    private fun ToggleableState(value: Boolean?) = when (value) {
+        true -> ToggleableState.On
+        false -> ToggleableState.Off
+        null -> ToggleableState.Indeterminate
+    }
+
+    companion object {
+        @Composable
+        fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+            model: SwitchPreferenceModel,
+            restrictions: Restrictions,
+            content: @Composable (SwitchPreferenceModel) -> Unit,
+        ) {
+            val context = LocalContext.current
+            val restrictedMode = rememberRestrictedMode(restrictions).value
+            val restrictedSwitchPreferenceModel = remember(restrictedMode) {
+                RestrictedSwitchPreferenceModel(context, model, restrictedMode)
+            }
+            restrictedSwitchPreferenceModel.RestrictionWrapper {
+                content(restrictedSwitchPreferenceModel)
+            }
+        }
+
+        fun getSummary(
+            context: Context,
+            restrictedModeSupplier: () -> RestrictedMode?,
+            summaryIfNoRestricted: () -> String,
+            checked: () -> Boolean?,
+        ): () -> String = {
+            when (val restrictedMode = restrictedModeSupplier()) {
+                is NoRestricted -> summaryIfNoRestricted()
+                is BaseUserRestricted ->
+                    context.getString(com.android.settingslib.R.string.disabled)
+
+                is BlockedByAdmin -> restrictedMode.getSummary(checked())
+                null -> context.getPlaceholder()
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
new file mode 100644
index 0000000..8fd16b3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.enterprise
+
+import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.RestrictedLockUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedModeTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val fakeEnterpriseRepository = object : IEnterpriseRepository {
+        override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+            when (updatableStringId) {
+                Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY -> ENABLED_BY_ADMIN
+                Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY -> DISABLED_BY_ADMIN
+                else -> ""
+            }
+    }
+
+    @Test
+    fun blockedByAdmin_getSummaryWhenChecked() {
+        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+
+        val summary = blockedByAdmin.getSummary(true)
+
+        assertThat(summary).isEqualTo(ENABLED_BY_ADMIN)
+    }
+
+    @Test
+    fun blockedByAdmin_getSummaryNotWhenChecked() {
+        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+
+        val summary = blockedByAdmin.getSummary(false)
+
+        assertThat(summary).isEqualTo(DISABLED_BY_ADMIN)
+    }
+
+    private companion object {
+        const val RESTRICTION = "restriction"
+        val ENFORCED_ADMIN: RestrictedLockUtils.EnforcedAdmin =
+            RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+        const val ENABLED_BY_ADMIN = "Enabled by admin"
+        const val DISABLED_BY_ADMIN = "Disabled by admin"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index ab34f68..72a5bd7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -105,7 +105,8 @@
             }
         }
 
-        composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
+        composeTestRule.onNodeWithText(text = "version $VERSION_NAME", substring = true)
+            .assertIsDisplayed()
     }
 
     @Test
@@ -119,10 +120,10 @@
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
-                appInfoProvider.FooterAppVersion(true)
+                appInfoProvider.FooterAppVersion(showPackageName = true)
             }
         }
-        composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed()
+        composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertIsDisplayed()
     }
 
 
@@ -137,10 +138,10 @@
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
-                appInfoProvider.FooterAppVersion(false)
+                appInfoProvider.FooterAppVersion(showPackageName = false)
             }
         }
-        composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist()
+        composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertDoesNotExist()
     }
 
     private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 3b2fe0f..d158a24 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -32,44 +32,37 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class AppOpPermissionAppListTest {
-    @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+    @get:Rule
+    val composeTestRule = createComposeRule()
 
-    @get:Rule val composeTestRule = createComposeRule()
+    private val packageManagers = mock<IPackageManagers>()
 
-    @Spy private val context: Context = ApplicationProvider.getApplicationContext()
+    private val appOpsManager = mock<AppOpsManager>()
 
-    @Mock private lateinit var packageManagers: IPackageManagers
-
-    @Mock private lateinit var appOpsManager: AppOpsManager
-
-    @Mock private lateinit var packageManager: PackageManager
-
-    private lateinit var listModel: TestAppOpPermissionAppListModel
-
-    @Before
-    fun setUp() {
-        whenever(context.appOpsManager).thenReturn(appOpsManager)
-        whenever(context.packageManager).thenReturn(packageManager)
-        doNothing().whenever(packageManager)
-                .updatePermissionFlags(any(), any(), any(), any(), any())
-        listModel = TestAppOpPermissionAppListModel()
+    private val packageManager = mock<PackageManager> {
+        doNothing().whenever(mock).updatePermissionFlags(any(), any(), any(), any(), any())
     }
 
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { appOpsManager } doReturn appOpsManager
+        on { packageManager } doReturn packageManager
+    }
+
+    private val listModel = TestAppOpPermissionAppListModel()
+
     @Test
     fun transformItem_recordHasCorrectApp() {
         val record = listModel.transformItem(APP)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index 8bfae14..270b3fa 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -38,44 +38,34 @@
 import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
 import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.whenever
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 
 @RunWith(AndroidJUnit4::class)
 class TogglePermissionAppInfoPageTest {
     @get:Rule
     val composeTestRule = createComposeRule()
 
-    @get:Rule
-    val mockito: MockitoRule = MockitoJUnit.rule()
-
     private val context: Context = ApplicationProvider.getApplicationContext()
 
-    @Mock
-    private lateinit var packageManagers: IPackageManagers
+    private val packageManagers = mock<IPackageManagers> {
+        on { getPackageInfoAsUser(PACKAGE_NAME, USER_ID) } doReturn PACKAGE_INFO
+    }
 
     private val fakeNavControllerWrapper = FakeNavControllerWrapper()
 
-    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider().apply {
+        restrictedMode = NoRestricted
+    }
 
     private val appListTemplate =
         TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
 
     private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate)
 
-    @Before
-    fun setUp() {
-        fakeRestrictionsProvider.restrictedMode = NoRestricted
-        whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID))
-            .thenReturn(PACKAGE_INFO)
-    }
-
     @Test
     fun buildEntry() {
         val entryList = appInfoPageProvider.buildEntry(null)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
new file mode 100644
index 0000000..55c16bd
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedMainSwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val switchPreferenceModel = object : SwitchPreferenceModel {
+        override val title = TITLE
+        private val checkedState = mutableStateOf(true)
+        override val checked = { checkedState.value }
+        override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notToggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertDoesNotExist()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        composeTestRule.setContent {
+            RestrictedMainSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml
new file mode 100644
index 0000000..bcf5b91
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:fillColor="#4E4639"
+        android:pathData="M21.818,18.14V15.227C21.818,12.522 19.615,10.318 16.909,10.318C14.204,10.318 12,12.522 12,15.227V19.591C12,22.296 14.204,24.5 16.909,24.5C17.662,24.5 18.382,24.326 19.025,24.02C19.538,24.326 20.127,24.5 20.727,24.5C22.527,24.5 24,23.028 24,21.228C24,19.809 23.084,18.587 21.818,18.14ZM16.909,12.5C18.414,12.5 19.636,13.722 19.636,15.227C19.636,16.733 18.414,17.955 16.909,17.955C15.404,17.955 14.182,16.733 14.182,15.227C14.182,13.722 15.404,12.5 16.909,12.5ZM14.182,19.591V19.308C14.967,19.831 15.906,20.136 16.909,20.136C17.913,20.136 18.851,19.831 19.636,19.308V19.591C19.636,19.635 19.636,19.678 19.636,19.711C19.636,19.722 19.636,19.733 19.636,19.744C19.636,19.777 19.636,19.82 19.625,19.853C19.625,19.864 19.625,19.886 19.625,19.896C19.625,19.918 19.615,19.951 19.615,19.973C19.615,19.995 19.604,20.028 19.604,20.049C19.604,20.06 19.593,20.082 19.593,20.093C19.549,20.3 19.494,20.497 19.407,20.693C19.385,20.736 19.364,20.78 19.342,20.824C19.342,20.835 19.331,20.846 19.331,20.846C19.32,20.867 19.309,20.889 19.298,20.911C19.287,20.933 19.265,20.966 19.254,20.987C19.244,20.998 19.244,21.009 19.233,21.02C19.211,21.053 19.2,21.075 19.178,21.107C19.178,21.107 19.178,21.108 19.178,21.118C18.687,21.838 17.858,22.318 16.92,22.318C15.404,22.318 14.182,21.097 14.182,19.591Z" />
+    <path
+        android:fillColor="#4E4639"
+        android:pathData="M12,5.409C12,2.704 9.796,0.5 7.091,0.5C4.385,0.5 2.182,2.704 2.182,5.409V8.322C0.916,8.769 0,9.991 0,11.409C0,13.209 1.473,14.682 3.273,14.682C3.873,14.682 4.462,14.507 4.975,14.202C5.618,14.507 6.338,14.682 7.091,14.682C9.796,14.682 12,12.478 12,9.773V5.409ZM7.091,2.682C8.596,2.682 9.818,3.904 9.818,5.409C9.818,6.915 8.596,8.136 7.091,8.136C5.585,8.136 4.364,6.915 4.364,5.409C4.364,3.904 5.585,2.682 7.091,2.682ZM7.091,12.5C6.153,12.5 5.324,12.02 4.833,11.3C4.833,11.3 4.833,11.3 4.833,11.289C4.811,11.256 4.8,11.234 4.778,11.202C4.767,11.191 4.767,11.18 4.756,11.169C4.745,11.147 4.724,11.115 4.713,11.093C4.702,11.071 4.691,11.049 4.68,11.027C4.68,11.016 4.669,11.005 4.669,11.005C4.647,10.962 4.625,10.918 4.604,10.875C4.516,10.678 4.451,10.482 4.418,10.274C4.418,10.264 4.407,10.242 4.407,10.231C4.407,10.209 4.396,10.176 4.396,10.155C4.396,10.133 4.385,10.1 4.385,10.078C4.385,10.067 4.385,10.045 4.385,10.035C4.385,10.002 4.375,9.958 4.375,9.925C4.375,9.915 4.375,9.904 4.375,9.893C4.364,9.86 4.364,9.816 4.364,9.773V9.489C5.149,10.013 6.087,10.318 7.091,10.318C8.095,10.318 9.033,10.013 9.818,9.489V9.773C9.818,11.278 8.596,12.5 7.091,12.5Z" />
+</vector>
+
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index ea74468..de703e9 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -437,8 +437,8 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Паказваць апавяшчэнні пра перакадзіраванне"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Адключыць кэш перакадзіравання"</string>
     <string name="widevine_settings_title" msgid="4023329801172572917">"Налады Widevine"</string>
-    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Прымусовае выкарыстанне альтэрнатывы L3"</string>
-    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Выберыце, ці трэба прымусова выкарыстоўваць альтэрнатыву L3"</string>
+    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Прымусовы пераход на ўзровень бяспекі 3"</string>
+    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Выберыце, каб прымусова пераходзіць на ўзровень бяспекі 3"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Запушчаныя службы"</string>
     <string name="runningservices_settings_summary" msgid="1046080643262665743">"Прагляд запушчаных службаў i кіраванне iмi"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"Рэалізацыя WebView"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 3dc3943..6e12771 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -567,7 +567,7 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Forbundet via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Forbundet via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="38102257053315304">"Fjernsyn som standardoutput"</string>
+    <string name="tv_media_transfer_default" msgid="38102257053315304">"Fjernsyn som standard"</string>
     <string name="tv_media_transfer_hdmi" msgid="2229000864416329818">"HDMI-udgang"</string>
     <string name="tv_media_transfer_internal_speakers" msgid="2433193551482972117">"Interne højttalere"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Der kunne ikke oprettes forbindelse. Sluk og tænd enheden"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index a2cafee..e5c8242 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -437,8 +437,8 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Kuva transkodeerimise märguanded"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Transkodeerimise vahemälu keelamine"</string>
     <string name="widevine_settings_title" msgid="4023329801172572917">"Widevine\'i seaded"</string>
-    <string name="force_l3_fallback_title" msgid="4987972688770202547">"L3 varuvariandiks sundimine"</string>
-    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Valige, kas sundida L3 varuvariandiks"</string>
+    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Sundtaane tasemele L3"</string>
+    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Valige sundtaandeks tasemele L3"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Käitatud teenused"</string>
     <string name="runningservices_settings_summary" msgid="1046080643262665743">"Praegu käitatud teenuste vaatamine ja juhtimine"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView\' rakendamine"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 7b71639..8f5c042 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -634,7 +634,7 @@
     <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ಅತಿಥಿ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬೇಕೆ?"</string>
     <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ನೀವು ಪ್ರಸ್ತುತ ಸೆಶನ್‌ನ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬಹುದು ಅಥವಾ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಬಹುದು"</string>
     <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ಅಳಿಸಿ"</string>
-    <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ಉಳಿಸಿ"</string>
+    <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="guest_exit_button" msgid="5774985819191803960">"ಅತಿಥಿ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
     <string name="guest_reset_button" msgid="2515069346223503479">"ಅತಿಥಿ ಸೆಷನ್ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಿ"</string>
     <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ಅತಿಥಿ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 2ef4620..75dbbf1 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -437,8 +437,8 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Прикажувај известувања за транскодирање"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Оневозможи го кешот на транскодирањето"</string>
     <string name="widevine_settings_title" msgid="4023329801172572917">"Поставки за Widevine"</string>
-    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Резервен план за Force L3"</string>
-    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Изберете за да се овозможи резервен план за Force L3"</string>
+    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Наметни алтернативно безбедносно ниво L3"</string>
+    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Изберете за да се наметне алтернативно безбедносно ниво L3"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Активни услуги"</string>
     <string name="runningservices_settings_summary" msgid="1046080643262665743">"Погледнете и контролирајте услуги што се моментално активни"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"Примена на WebView"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index e357543..0a99650 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -437,7 +437,7 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Хөрвүүлгийн мэдэгдэл харуулах"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Хөрвүүлгийн завсрын санах ойг идэвхгүй болгох"</string>
     <string name="widevine_settings_title" msgid="4023329801172572917">"Widevine-н тохиргоо"</string>
-    <string name="force_l3_fallback_title" msgid="4987972688770202547">"L3 нөөцийг хүчилнэ үү"</string>
+    <string name="force_l3_fallback_title" msgid="4987972688770202547">"L3 нөөцийг хүчлэх"</string>
     <string name="force_l3_fallback_summary" msgid="3080790841069996016">"L3 нөөцийг хүчлэхийг сонгоно уу"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Ажиллаж байгаа үйлчилгээнүүд"</string>
     <string name="runningservices_settings_summary" msgid="1046080643262665743">"Одоо ажиллаж байгаа үйлчилгээнүүдийг харах болон хянах"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 65e4b2e..185870a 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -683,7 +683,7 @@
     <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string>
     <string name="accessibility_no_calling" msgid="3540827068323895748">"କୌଣସି କଲିଂ ନାହିଁ।"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
-    <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string>
+    <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ୟୁଜର ଆଇକନ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ଫିଜିକାଲ କୀବୋର୍ଡ"</string>
     <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"କୀବୋର୍ଡ ଲେଆଉଟ ବାଛନ୍ତୁ"</string>
     <string name="keyboard_layout_default_label" msgid="1997292217218546957">"ଡିଫଲ୍ଟ"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index fb870c4..e96c49f 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -437,8 +437,8 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Hiện thông báo chuyển mã"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Vô hiệu hóa bộ nhớ đệm dùng để chuyển mã"</string>
     <string name="widevine_settings_title" msgid="4023329801172572917">"Cài đặt Widevine"</string>
-    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Buộc chuyển sang phương án dự phòng L3"</string>
-    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Chọn để buộc chuyển sang phương án dự phòng L3"</string>
+    <string name="force_l3_fallback_title" msgid="4987972688770202547">"Buộc sử dụng mức bảo mật L3"</string>
+    <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Chọn để buộc sử dụng mức bảo mật L3"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Các dịch vụ đang chạy"</string>
     <string name="runningservices_settings_summary" msgid="1046080643262665743">"Xem và kiểm soát các dịch vụ đang chạy"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"Triển khai WebView"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1a5acf6..5aa2bfc 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1373,11 +1373,11 @@
     <string name="tv_media_transfer_earc_subtitle">Connected via eARC</string>
 
     <!-- TV media output switcher. Title for the default audio output of the device [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_default">TV Default</string>
+    <string name="tv_media_transfer_default">TV default</string>
     <!-- TV media output switcher. Subtitle for default audio output which is HDMI, e.g. TV dongle [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_hdmi">HDMI Output</string>
+    <string name="tv_media_transfer_hdmi">HDMI output</string>
     <!-- TV media output switcher. Subtitle for default audio output which is internal speaker, i.e. panel VTs [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_internal_speakers">Internal Speakers</string>
+    <string name="tv_media_transfer_internal_speakers">Internal speakers</string>
 
     <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
     <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off &amp; back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index f5bacb6..c97445f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -230,30 +230,30 @@
 
     @VisibleForTesting
     void dispatchActiveDeviceChanged(
-            @Nullable CachedBluetoothDevice activeDevice,
-            int bluetoothProfile) {
+            @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+        CachedBluetoothDevice targetDevice = activeDevice;
         for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
-            Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
-            boolean isActive = Objects.equals(cachedDevice, activeDevice);
-            if (!isActive && !memberSet.isEmpty()) {
-                for (CachedBluetoothDevice memberCachedDevice : memberSet) {
-                    isActive = Objects.equals(memberCachedDevice, activeDevice);
-                    if (isActive) {
-                        Log.d(TAG,
-                                "The active device is the member device "
-                                        + activeDevice.getDevice().getAnonymizedAddress()
-                                        + ". change activeDevice as main device "
-                                        + cachedDevice.getDevice().getAnonymizedAddress());
-                        activeDevice = cachedDevice;
-                        break;
-                    }
-                }
+            // should report isActive from main device or it will cause trouble to other callers.
+            CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+            CachedBluetoothDevice finalTargetDevice = targetDevice;
+            if (targetDevice != null
+                    && ((subDevice != null && subDevice.equals(targetDevice))
+                    || cachedDevice.getMemberDevice().stream().anyMatch(
+                            memberDevice -> memberDevice.equals(finalTargetDevice)))) {
+                Log.d(TAG,
+                        "The active device is the sub/member device "
+                                + targetDevice.getDevice().getAnonymizedAddress()
+                                + ". change targetDevice as main device "
+                                + cachedDevice.getDevice().getAnonymizedAddress());
+                targetDevice = cachedDevice;
             }
-            cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
+            boolean isActiveDevice = cachedDevice.equals(targetDevice);
+            cachedDevice.onActiveDeviceChanged(isActiveDevice, bluetoothProfile);
             mDeviceManager.onActiveDeviceChanged(cachedDevice);
         }
+
         for (BluetoothCallback callback : mCallbacks) {
-            callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
+            callback.onActiveDeviceChanged(targetDevice, bluetoothProfile);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index f83e37b..3774b88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -37,9 +37,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
-/**
- * VolumeControlProfile handles Bluetooth Volume Control Controller role
- */
+/** VolumeControlProfile handles Bluetooth Volume Control Controller role */
 public class VolumeControlProfile implements LocalBluetoothProfile {
     private static final String TAG = "VolumeControlProfile";
     private static boolean DEBUG = true;
@@ -77,8 +75,8 @@
                     }
                     device = mDeviceManager.addDevice(nextDevice);
                 }
-                device.onProfileStateChanged(VolumeControlProfile.this,
-                        BluetoothProfile.STATE_CONNECTED);
+                device.onProfileStateChanged(
+                        VolumeControlProfile.this, BluetoothProfile.STATE_CONNECTED);
                 device.refresh();
             }
 
@@ -95,32 +93,36 @@
         }
     }
 
-    VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+    VolumeControlProfile(
+            Context context,
+            CachedBluetoothDeviceManager deviceManager,
             LocalBluetoothProfileManager profileManager) {
         mContext = context;
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
 
-        BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
-                new VolumeControlProfile.VolumeControlProfileServiceListener(),
-                BluetoothProfile.VOLUME_CONTROL);
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(
+                        context,
+                        new VolumeControlProfile.VolumeControlProfileServiceListener(),
+                        BluetoothProfile.VOLUME_CONTROL);
     }
 
-
     /**
-     * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
-     * operation of this profile.
+     * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the operation
+     * of this profile.
      *
-     * Repeated registration of the same <var>callback</var> object will have no effect after
-     * the first call to this method, even when the <var>executor</var> is different. API caller
-     * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with
-     * the same callback object before registering it again.
+     * <p>Repeated registration of the same <var>callback</var> object will have no effect after the
+     * first call to this method, even when the <var>executor</var> is different. API caller would
+     * have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with the same
+     * callback object before registering it again.
      *
      * @param executor an {@link Executor} to execute given callback
      * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
      * @throws IllegalArgumentException if a null executor or callback is given
      */
-    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+    public void registerCallback(
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothVolumeControl.Callback callback) {
         if (mService == null) {
             Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
@@ -131,8 +133,9 @@
 
     /**
      * Unregisters the specified {@link BluetoothVolumeControl.Callback}.
-     * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
-     * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+     *
+     * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling {@link
+     * #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
      *
      * <p>Callbacks are automatically unregistered when application process goes away
      *
@@ -153,8 +156,8 @@
      * @param device {@link BluetoothDevice} representing the remote device
      * @param volumeOffset volume offset to be set on the remote device
      */
-    public void setVolumeOffset(BluetoothDevice device,
-            @IntRange(from = -255, to = 255) int volumeOffset) {
+    public void setVolumeOffset(
+            BluetoothDevice device, @IntRange(from = -255, to = 255) int volumeOffset) {
         if (mService == null) {
             Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
             return;
@@ -165,16 +168,13 @@
         }
         mService.setVolumeOffset(device, volumeOffset);
     }
-
     /**
-     * Provides information about the possibility to set volume offset on the remote device.
-     * If the remote device supports Volume Offset Control Service, it is automatically
-     * connected.
+     * Provides information about the possibility to set volume offset on the remote device. If the
+     * remote device supports Volume Offset Control Service, it is automatically connected.
      *
      * @param device {@link BluetoothDevice} representing the remote device
      * @return {@code true} if volume offset function is supported and available to use on the
-     *         remote device. When Bluetooth is off, the return value should always be
-     *         {@code false}.
+     *     remote device. When Bluetooth is off, the return value should always be {@code false}.
      */
     public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
         if (mService == null) {
@@ -188,6 +188,28 @@
         return mService.isVolumeOffsetAvailable(device);
     }
 
+    /**
+     * Tells the remote device to set a volume.
+     *
+     * @param device {@link BluetoothDevice} representing the remote device
+     * @param volume volume to be set on the remote device
+     * @param isGroupOp whether to set the volume to remote devices within the same CSIP group
+     */
+    public void setDeviceVolume(
+            BluetoothDevice device,
+            @IntRange(from = 0, to = 255) int volume,
+            boolean isGroupOp) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+            return;
+        }
+        if (device == null) {
+            Log.w(TAG, "Device is null. Cannot set volume offset.");
+            return;
+        }
+        mService.setDeviceVolume(device, volume, isGroupOp);
+    }
+
     @Override
     public boolean accessProfileEnabled() {
         return false;
@@ -199,10 +221,9 @@
     }
 
     /**
-     * Gets VolumeControlProfile devices matching connection states{
-     * {@code BluetoothProfile.STATE_CONNECTED},
-     * {@code BluetoothProfile.STATE_CONNECTING},
-     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     * Gets VolumeControlProfile devices matching connection states{ {@code
+     * BluetoothProfile.STATE_CONNECTED}, {@code BluetoothProfile.STATE_CONNECTING}, {@code
+     * BluetoothProfile.STATE_DISCONNECTING}}
      *
      * @return Matching device list
      */
@@ -211,8 +232,11 @@
             return new ArrayList<BluetoothDevice>(0);
         }
         return mService.getDevicesMatchingConnectionStates(
-                new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
-                        BluetoothProfile.STATE_DISCONNECTING});
+                new int[] {
+                    BluetoothProfile.STATE_CONNECTED,
+                    BluetoothProfile.STATE_CONNECTING,
+                    BluetoothProfile.STATE_DISCONNECTING
+                });
     }
 
     @Override
@@ -285,7 +309,7 @@
 
     @Override
     public int getSummaryResourceForDevice(BluetoothDevice device) {
-        return 0;   // VCP profile not displayed in UI
+        return 0; // VCP profile not displayed in UI
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 83c106b..92db508 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.provider.Settings;
+import android.os.UserManager;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityManager;
 
@@ -68,4 +69,10 @@
         }
         return packageNames;
     }
+
+    /** Returns true if current user is a work profile user. */
+    public static boolean isWorkProfile(Context context) {
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        return userManager.isManagedProfile() && !userManager.isSystemUser();
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 5dacba5..52b51d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -413,12 +413,8 @@
      */
     @NonNull
     List<MediaDevice> getSelectedMediaDevices() {
-        if (TextUtils.isEmpty(mPackageName)) {
-            Log.w(TAG, "getSelectedMediaDevices() package name is null or empty!");
-            return Collections.emptyList();
-        }
+        RoutingSessionInfo info = getRoutingSessionInfo();
 
-        final RoutingSessionInfo info = getRoutingSessionInfo();
         if (info == null) {
             Log.w(TAG, "getSelectedMediaDevices() cannot find selectable MediaDevice from : "
                     + mPackageName);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
index 3514932..02ec90d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java
@@ -48,6 +48,17 @@
             "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG";
 
     /**
+     * An intent action to launch a media output dialog without any app or playback metadata, which
+     * only controls system routing.
+     *
+     * <p>System routes are those provided by the system, such as built-in speakers, wired headsets,
+     * bluetooth devices, and other outputs that require the app to feed media samples to the
+     * framework.
+     */
+    public static final String ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG =
+            "com.android.systemui.action.LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG";
+
+    /**
      * An intent action to launch media output broadcast dialog.
      */
     public static final String ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 8c316d1..13635c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -412,6 +412,21 @@
     }
 
     @Test
+    public void dispatchActiveDeviceChanged_activeFromSubDevice_mainCachedDeviceActive() {
+        CachedBluetoothDevice subDevice = new CachedBluetoothDevice(mContext, mLocalProfileManager,
+                mDevice3);
+        mCachedDevice1.setSubDevice(subDevice);
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        mCachedDevice1.onProfileStateChanged(mHearingAidProfile,
+                BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
+        mBluetoothEventManager.dispatchActiveDeviceChanged(subDevice, BluetoothProfile.HEARING_AID);
+        assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue();
+    }
+
+    @Test
     public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index c560627..fe1529d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -54,18 +54,14 @@
 public class VolumeControlProfileTest {
 
     private static final int TEST_VOLUME_OFFSET = 10;
+    private static final int TEST_VOLUME_VALUE = 10;
 
-    @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private CachedBluetoothDeviceManager mDeviceManager;
-    @Mock
-    private LocalBluetoothProfileManager mProfileManager;
-    @Mock
-    private BluetoothDevice mBluetoothDevice;
-    @Mock
-    private BluetoothVolumeControl mService;
+    @Mock private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock private LocalBluetoothProfileManager mProfileManager;
+    @Mock private BluetoothDevice mBluetoothDevice;
+    @Mock private BluetoothVolumeControl mService;
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private BluetoothProfile.ServiceListener mServiceListener;
@@ -177,14 +173,14 @@
     @Test
     public void getConnectedDevices_returnCorrectList() {
         mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
-        int[] connectedStates = new int[] {
-                BluetoothProfile.STATE_CONNECTED,
-                BluetoothProfile.STATE_CONNECTING,
-                BluetoothProfile.STATE_DISCONNECTING};
-        List<BluetoothDevice> connectedList = Arrays.asList(
-                mBluetoothDevice,
-                mBluetoothDevice,
-                mBluetoothDevice);
+        int[] connectedStates =
+                new int[] {
+                    BluetoothProfile.STATE_CONNECTED,
+                    BluetoothProfile.STATE_CONNECTING,
+                    BluetoothProfile.STATE_DISCONNECTING
+                };
+        List<BluetoothDevice> connectedList =
+                Arrays.asList(mBluetoothDevice, mBluetoothDevice, mBluetoothDevice);
         when(mService.getDevicesMatchingConnectionStates(connectedStates))
                 .thenReturn(connectedList);
 
@@ -222,6 +218,16 @@
     }
 
     @Test
+    public void setDeviceVolume_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+        mProfile.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+
+        verify(mService)
+                .setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+    }
+
+    @Test
     public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
         mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
         when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index 21cdc49..b3d66aa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -259,8 +259,10 @@
     }
 
     @Test
-    public void isSearchable_noMetadata_isTrue() {
-        final Tile tile = new ActivityTile(null, "category");
+    public void isSearchable_nullMetadata_isTrue() {
+        mActivityInfo.metaData = null;
+
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
 
         assertThat(tile.isSearchable()).isTrue();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index faccf2f..90140d4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -259,13 +259,6 @@
     }
 
     @Test
-    public void isSearchable_noMetadata_isTrue() {
-        final Tile tile = new ProviderTile(mProviderInfo, "category", null);
-
-        assertThat(tile.isSearchable()).isTrue();
-    }
-
-    @Test
     public void isSearchable_notSet_isTrue() {
         final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
index c3e0c0b..6424352 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.view.accessibility.AccessibilityManager;
 
@@ -40,6 +41,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
 import org.robolectric.shadows.ShadowAccessibilityManager;
 
 import java.util.Arrays;
@@ -99,6 +101,20 @@
                 .containsExactly(DEFAULT_TTS_PACKAGE, ACCESSIBILITY_PACKAGE);
     }
 
+    @Test
+    public void isWorkProfile_defaultValue_returnFalse() {
+        assertThat(BatteryUtils.isWorkProfile(mContext)).isFalse();
+    }
+
+    @Test
+    public void isWorkProfile_workProfileMode_returnTrue() {
+        final UserManager userManager = mContext.getSystemService(UserManager.class);
+        Shadows.shadowOf(userManager).setManagedProfile(true);
+        Shadows.shadowOf(userManager).setIsSystemUser(false);
+
+        assertThat(BatteryUtils.isWorkProfile(mContext)).isTrue();
+    }
+
     private void setTtsPackageName(String defaultTtsPackageName) {
         Settings.Secure.putString(mContext.getContentResolver(),
                 Settings.Secure.TTS_DEFAULT_SYNTH, defaultTtsPackageName);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 0cabab2..542f101 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -168,10 +168,10 @@
     private static final String EXPECTED_NEW_HTML_STRING = HTML_HEAD_STRING + HTML_NEW_BODY_STRING;
 
     private static final String EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING =
-            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_OLD_BODY_STRING;
+            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n<br/>\n" + HTML_OLD_BODY_STRING;
 
     private static final String EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING =
-            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_NEW_BODY_STRING;
+            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n<br/>\n" + HTML_NEW_BODY_STRING;
 
     @Test
     public void testParseValidXmlStream() throws XmlPullParserException, IOException {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 721e69d..f0f53d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -39,9 +39,9 @@
 
 import androidx.annotation.ColorRes;
 import androidx.preference.PreferenceViewHolder;
-import com.android.settingslib.widget.preference.banner.R;
 
 import com.android.settingslib.testutils.OverpoweredReflectionHelper;
+import com.android.settingslib.widget.preference.banner.R;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2c15fc6..8412cba 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -249,6 +249,8 @@
         Settings.Secure.HUB_MODE_TUTORIAL_STATE,
         Settings.Secure.STYLUS_BUTTONS_ENABLED,
         Settings.Secure.STYLUS_HANDWRITING_ENABLED,
-        Settings.Secure.DEFAULT_NOTE_TASK_PROFILE
+        Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+        Settings.Secure.CREDENTIAL_SERVICE,
+        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 71c2ddc..9197554 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -19,11 +19,13 @@
 import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
@@ -62,7 +64,6 @@
         VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                 new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE));
@@ -398,5 +399,8 @@
         VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
         VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
+        VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
+        VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 49012b0..a8a659e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -235,4 +235,30 @@
             }
         }
     };
+
+    static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(String value) {
+            if (value == null || value.equals("")) {
+                return true;
+            }
+
+            return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value);
+        }
+    };
+
+    static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(String value) {
+            if (value == null || value.equals("")) {
+                return true;
+            }
+
+            if (value.equals("credential-provider")) {
+               return true;
+            }
+
+            return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value);
+        }
+    };
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 27a45df..3e0d05c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -391,6 +391,7 @@
             case Settings.Secure.TOUCH_EXPLORATION_ENABLED:
             case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
+            case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED:
             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED:
                 return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
             case Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES:
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f1b53ed..85e8769 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -235,6 +235,7 @@
                     Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
                     Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
                     Settings.Global.DEVICE_DEMO_MODE,
+                    Settings.Global.DEVICE_IDLE_CONSTANTS,
                     Settings.Global.DISABLE_WINDOW_BLURS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
                     Settings.Global.BATTERY_TIP_CONSTANTS,
@@ -851,8 +852,6 @@
                  Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
-                 Settings.Secure.CREDENTIAL_SERVICE,
-                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                  Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
                  Settings.Secure.DND_CONFIGS_MIGRATED,
                  Settings.Secure.NAVIGATION_MODE_RESTORE);
diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
index 865f431..3b3bf8ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -340,6 +340,60 @@
         failIfOffendersPresent(offenders, "Settings.Secure");
     }
 
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfNull() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfEmpty() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() {
+        assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"
+            + ":android.credentials/.Test2"));
+    }
+
+    @Test
+    public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() {
+        assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfNull() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfEmpty() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfPlaceholder() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() {
+        assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(
+            "android.credentials/android.credentials.Test"));
+    }
+
+    @Test
+    public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() {
+        assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test"));
+    }
+
     private void failIfOffendersPresent(String offenders, String settingsType) {
         if (offenders.length() > 0) {
             fail("All " + settingsType + " settings that are backed up have to have a non-null"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ed03d94..650319f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -19,6 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.shell"
         coreApp="true"
+        updatableSystem="false"
         android:sharedUserId="android.uid.shell"
         >
 
@@ -325,6 +326,7 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
+    <uses-permission android:name="android.permission.QUARANTINE_APPS" />
     <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
     <!-- Permission needed to wipe the device for Test Harness Mode -->
@@ -867,6 +869,8 @@
     <!-- Permissions required for CTS test - CtsVoiceInteractionTestCases -->
     <uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" />
     <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" />
+    <uses-permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" />
+
     <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" />
 
     <application
diff --git a/packages/Shell/res/values-kn/strings.xml b/packages/Shell/res/values-kn/strings.xml
index a6f61ed..56448f7 100644
--- a/packages/Shell/res/values-kn/strings.xml
+++ b/packages/Shell/res/values-kn/strings.xml
@@ -42,6 +42,6 @@
     <string name="bugreport_info_name" msgid="4414036021935139527">"ಫೈಲ್‌ಹೆಸರು"</string>
     <string name="bugreport_info_title" msgid="2306030793918239804">"ಬಗ್ ಶೀರ್ಷಿಕೆ"</string>
     <string name="bugreport_info_description" msgid="5072835127481627722">"ಬಗ್ ಸಾರಾಂಶ"</string>
-    <string name="save" msgid="4781509040564835759">"ಉಳಿಸಿ"</string>
+    <string name="save" msgid="4781509040564835759">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"ಬಗ್ ವರದಿಯನ್ನು ಹಂಚು"</string>
 </resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f1ffa66..d78038e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -602,6 +602,7 @@
         ":SystemUI-tests-multivalent",
     ],
     static_libs: [
+        "dagger2",
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e218308..0a71cda 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1044,6 +1044,7 @@
                   android:exported="true">
             <intent-filter android:priority="1">
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
+                <action android:name="com.android.systemui.action.LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG" />
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" />
                 <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" />
             </intent-filter>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0480b9d..0c89a5d 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -126,24 +126,6 @@
           "exclude-annotation": "android.platform.test.annotations.Postsubmit"
         }
       ]
-    },
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ]
     }
   ],
   
@@ -170,18 +152,6 @@
           "include-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
-    },
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
     }
   ]
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml
index cc6638b..6192d4a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml
@@ -9,7 +9,7 @@
     <string name="power_label" msgid="7699720321491287839">"電源"</string>
     <string name="power_utterance" msgid="7444296686402104807">"電源オプション"</string>
     <string name="recent_apps_label" msgid="6583276995616385847">"最近使ったアプリ"</string>
-    <string name="lockscreen_label" msgid="648347953557887087">"ロック画面"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"画面をロック"</string>
     <string name="quick_settings_label" msgid="2999117381487601865">"クイック設定"</string>
     <string name="notifications_label" msgid="6829741046963013567">"通知"</string>
     <string name="screenshot_label" msgid="863978141223970162">"スクリーンショット"</string>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e340209..3e84597 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -61,6 +61,13 @@
 }
 
 flag {
+    name: "notification_throttle_hun"
+    namespace: "systemui"
+    description: "During notification avalanche, throttle HUNs showing in fast succession."
+    bug: "307288824"
+}
+
+flag {
     name: "scene_container"
     namespace: "systemui"
     description: "Enables the scene container framework go/flexiglass."
@@ -107,6 +114,13 @@
 }
 
 flag {
+    name: "unfold_animation_background_progress"
+    namespace: "systemui"
+    description: "Moves unfold animation progress calculation to a background thread"
+    bug: "277879146"
+}
+
+flag {
     name: "qs_new_pipeline"
     namespace: "systemui"
     description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
@@ -119,4 +133,40 @@
     description: "Adds thread-local data to System UI's global coroutine scopes to "
         "allow for tracing of coroutine continuations using System UI's tracinglib"
     bug: "289353932"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "new_aod_transition"
+    namespace: "systemui"
+    description: "New LOCKSCREEN <=> AOD transition"
+    bug: "301915812"
+}
+
+flag {
+    name: "light_reveal_migration"
+    namespace: "systemui"
+    description: "Move LightRevealScrim to recommended architecture"
+    bug: "281655028"
+}
+
+flag {
+   name: "theme_overlay_controller_wakefulness_deprecation"
+   namespace: "systemui"
+   description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in "
+        "ThemOverlayController to mitigate flickering when locking the device"
+   bug: "308676488"
+}
+
+flag {
+   name: "media_in_scene_container"
+   namespace: "systemui"
+   description: "Enable media in the scene container framework"
+   bug: "296122467"
+}
+
+flag {
+   name: "record_issue_qs_tile"
+   namespace: "systemui"
+   description: "Replace Record Trace QS Tile with expanded Record Issue QS Tile"
+   bug: "305049544"
+}
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
deleted file mode 100644
index 88dad66..0000000
--- a/packages/SystemUI/communal/layout/Android.bp
+++ /dev/null
@@ -1,35 +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 {
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "CommunalLayoutLib",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "androidx.arch.core_core-runtime",
-        "androidx.compose.animation_animation-graphics",
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "jsr330",
-        "kotlinx-coroutines-android",
-        "kotlinx-coroutines-core",
-    ],
-    manifest: "AndroidManifest.xml",
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
deleted file mode 100644
index 91fe33c..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
+++ /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.systemui.communal.layout
-
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-
-/** Computes the arrangement of cards. */
-class CommunalLayoutEngine {
-    companion object {
-        /**
-         * Determines the size that each card should be rendered in, and distributes the cards into
-         * columns.
-         *
-         * Returns a nested list where the outer list contains columns, and the inner list contains
-         * cards in each column.
-         *
-         * Currently treats the first supported size as the size to be rendered in, ignoring other
-         * supported sizes.
-         *
-         * Cards are ordered by priority, from highest to lowest.
-         */
-        fun distributeCardsIntoColumns(
-            cards: List<CommunalGridLayoutCard>,
-        ): List<List<CommunalGridLayoutCardInfo>> {
-            val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
-
-            var capacityOfLastColumn = 0
-            val sorted = cards.sortedByDescending { it.priority }
-            for (card in sorted) {
-                val cardSize = card.supportedSizes.first()
-                if (capacityOfLastColumn >= cardSize.value) {
-                    // Card fits in last column
-                    capacityOfLastColumn -= cardSize.value
-                } else {
-                    // Create a new column
-                    result.add(arrayListOf())
-                    capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
-                }
-
-                result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
-            }
-
-            return result
-        }
-    }
-
-    /**
-     * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
-     * card should be rendered in.
-     */
-    data class CommunalGridLayoutCardInfo(
-        val card: CommunalGridLayoutCard,
-        val size: CommunalGridLayoutCard.Size,
-    )
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
deleted file mode 100644
index 33024f7..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ /dev/null
@@ -1,72 +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.systemui.communal.layout.ui.compose
-
-import android.util.SizeF
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.communal.layout.CommunalLayoutEngine
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
-
-/**
- * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
- * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
- */
-@Composable
-fun CommunalGridLayout(
-    modifier: Modifier,
-    layoutConfig: CommunalGridLayoutConfig,
-    communalCards: List<CommunalGridLayoutCard>,
-) {
-    val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
-    LazyRow(
-        modifier = modifier.height(layoutConfig.gridHeight),
-        horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
-    ) {
-        for (column in columns) {
-            item {
-                Column(
-                    modifier = Modifier.width(layoutConfig.cardWidth),
-                    verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
-                ) {
-                    for (cardInfo in column) {
-                        Row(
-                            modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
-                        ) {
-                            cardInfo.card.Content(
-                                modifier = Modifier.fillMaxSize(),
-                                size =
-                                    SizeF(
-                                        layoutConfig.cardWidth.value,
-                                        layoutConfig.cardHeight(cardInfo.size).value,
-                                    ),
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
deleted file mode 100644
index 4b2a156..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ /dev/null
@@ -1,68 +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.systemui.communal.layout.ui.compose.config
-
-import android.util.SizeF
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/** A card that hosts content to be rendered in the communal grid layout. */
-abstract class CommunalGridLayoutCard {
-    /**
-     * Content to be hosted by the card.
-     *
-     * To host non-Compose views, see
-     * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
-     *
-     * @param size The size given to the card. Content of the card should fill all this space, given
-     *   that margins and paddings have been taken care of by the layout.
-     */
-    @Composable abstract fun Content(modifier: Modifier, size: SizeF)
-
-    /**
-     * Sizes supported by the card.
-     *
-     * If multiple sizes are available, they should be ranked in order of preference, from most to
-     * least preferred.
-     */
-    abstract val supportedSizes: List<Size>
-
-    /**
-     * Priority of the content hosted by the card.
-     *
-     * The value of priority is relative to other cards. Cards with a higher priority are generally
-     * ordered first.
-     */
-    open val priority: Int = 0
-
-    /**
-     * Size of the card.
-     *
-     * @param value A numeric value that represents the size. Must be less than or equal to
-     *   [Size.FULL].
-     */
-    enum class Size(val value: Int) {
-        /** The card takes up full height of the grid layout. */
-        FULL(value = 6),
-
-        /** The card takes up half of the vertical space of the grid layout. */
-        HALF(value = 3),
-
-        /** The card takes up a third of the vertical space of the grid layout. */
-        THIRD(value = 2),
-    }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
deleted file mode 100644
index 143df83..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
+++ /dev/null
@@ -1,82 +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.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.times
-
-/**
- * Configurations of the communal grid layout.
- *
- * The communal grid layout follows Material Design's responsive layout grid (see
- * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
- * up by columns and gutters, and each card occupies one or multiple columns.
- */
-data class CommunalGridLayoutConfig(
-    /**
-     * Size in dp of each grid column.
-     *
-     * Every card occupies one or more grid columns, which means that the width of each card is
-     * influenced by the size of the grid columns.
-     */
-    val gridColumnSize: Dp,
-
-    /**
-     * Size in dp of each grid gutter.
-     *
-     * A gutter is the space between columns that helps separate content. This is, therefore, also
-     * the size of the gaps between cards, both horizontally and vertically.
-     */
-    val gridGutter: Dp,
-
-    /**
-     * Height in dp of the grid layout.
-     *
-     * Cards with a full size take up the entire height of the grid layout.
-     */
-    val gridHeight: Dp,
-
-    /**
-     * Number of grid columns that each card occupies.
-     *
-     * It's important to note that all the cards take up the same number of grid columns, or in
-     * simpler terms, they all have the same width.
-     */
-    val gridColumnsPerCard: Int,
-) {
-    /**
-     * Width in dp of each card.
-     *
-     * It's important to note that all the cards take up the same number of grid columns, or in
-     * simpler terms, they all have the same width.
-     */
-    val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
-
-    /** Returns the height of a card in dp, based on its size. */
-    fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
-        return when (cardSize) {
-            CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
-            CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
-            CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
-        }
-    }
-
-    /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
-    private fun cardHeightBy(denominator: Int): Dp {
-        return (gridHeight - (denominator - 1) * gridGutter) / denominator
-    }
-}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
deleted file mode 100644
index 9a05504..0000000
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ /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 {
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_test {
-    name: "CommunalLayoutLibTests",
-    srcs: [
-        "**/*.kt",
-    ],
-    static_libs: [
-        "CommunalLayoutLib",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "frameworks-base-testutils",
-        "junit",
-        "kotlinx_coroutines_test",
-        "mockito-target-extended-minus-junit4",
-        "platform-test-annotations",
-        "testables",
-        "truth",
-    ],
-    libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
-    ],
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-    manifest: "AndroidManifest.xml",
-}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
deleted file mode 100644
index b19007c..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.communal.layout.tests">
-
-    <application android:debuggable="true" android:largeHeap="true">
-        <uses-library android:name="android.test.mock" />
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.testing.TestableInstrumentation"
-        android:targetPackage="com.android.systemui.communal.layout.tests"
-        android:label="Tests for CommunalLayoutLib">
-    </instrumentation>
-
-</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
deleted file mode 100644
index 1352b23..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidTest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration description="Runs tests for CommunalLayoutLib">
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="install-arg" value="-t" />
-        <option name="test-file-name" value="CommunalLayoutLibTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="framework-base-presubmit" />
-    <option name="test-tag" value="CommunalLayoutLibTests" />
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.systemui.communal.layout.tests" />
-        <option name="runner" value="android.testing.TestableInstrumentation" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-
-</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
deleted file mode 100644
index 50b7c5f..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.android.systemui.communal.layout
-
-import android.util.SizeF
-import androidx.compose.material3.Card
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalLayoutEngineTest {
-    @Test
-    fun distribution_fullLayout() {
-        val cards =
-            listOf(
-                generateCard(CommunalGridLayoutCard.Size.FULL),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-            )
-        val expected =
-            listOf(
-                listOf(
-                    CommunalGridLayoutCard.Size.FULL,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                    CommunalGridLayoutCard.Size.HALF,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.THIRD,
-                    CommunalGridLayoutCard.Size.THIRD,
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-            )
-
-        assertDistributionBySize(cards, expected)
-    }
-
-    @Test
-    fun distribution_layoutWithGaps() {
-        val cards =
-            listOf(
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-                generateCard(CommunalGridLayoutCard.Size.HALF),
-                generateCard(CommunalGridLayoutCard.Size.FULL),
-                generateCard(CommunalGridLayoutCard.Size.THIRD),
-            )
-        val expected =
-            listOf(
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.HALF,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.FULL,
-                ),
-                listOf(
-                    CommunalGridLayoutCard.Size.THIRD,
-                ),
-            )
-
-        assertDistributionBySize(cards, expected)
-    }
-
-    @Test
-    fun distribution_sortByPriority() {
-        val cards =
-            listOf(
-                generateCard(priority = 2),
-                generateCard(priority = 7),
-                generateCard(priority = 10),
-                generateCard(priority = 1),
-                generateCard(priority = 5),
-            )
-        val expected =
-            listOf(
-                listOf(10, 7),
-                listOf(5, 2),
-                listOf(1),
-            )
-
-        assertDistributionByPriority(cards, expected)
-    }
-
-    private fun assertDistributionBySize(
-        cards: List<CommunalGridLayoutCard>,
-        expected: List<List<CommunalGridLayoutCard.Size>>,
-    ) {
-        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
-        for (c in expected.indices) {
-            for (r in expected[c].indices) {
-                assertThat(result[c][r].size).isEqualTo(expected[c][r])
-            }
-        }
-    }
-
-    private fun assertDistributionByPriority(
-        cards: List<CommunalGridLayoutCard>,
-        expected: List<List<Int>>,
-    ) {
-        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
-        for (c in expected.indices) {
-            for (r in expected[c].indices) {
-                assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
-            }
-        }
-    }
-
-    private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
-        return object : CommunalGridLayoutCard() {
-            override val supportedSizes = listOf(size)
-
-            @Composable
-            override fun Content(modifier: Modifier, size: SizeF) {
-                Card(modifier = modifier, content = {})
-            }
-        }
-    }
-
-    private fun generateCard(priority: Int): CommunalGridLayoutCard {
-        return object : CommunalGridLayoutCard() {
-            override val supportedSizes = listOf(Size.HALF)
-            override val priority = priority
-
-            @Composable
-            override fun Content(modifier: Modifier, size: SizeF) {
-                Card(modifier = modifier, content = {})
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
deleted file mode 100644
index 946eeec..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalGridLayoutConfigTest {
-    @Test
-    fun cardWidth() {
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 1,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(5.dp)
-
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 2,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(13.dp)
-
-        Truth.assertThat(
-                CommunalGridLayoutConfig(
-                        gridColumnSize = 5.dp,
-                        gridGutter = 3.dp,
-                        gridHeight = 17.dp,
-                        gridColumnsPerCard = 3,
-                    )
-                    .cardWidth
-            )
-            .isEqualTo(21.dp)
-    }
-
-    @Test
-    fun cardHeight() {
-        val config =
-            CommunalGridLayoutConfig(
-                gridColumnSize = 5.dp,
-                gridGutter = 2.dp,
-                gridHeight = 10.dp,
-                gridColumnsPerCard = 3,
-            )
-
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
-        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
-    }
-}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index ddd1c67..914e5f2 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,7 +22,7 @@
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
@@ -47,6 +47,14 @@
         throwComposeUnavailableError()
     }
 
+    override fun setCommunalEditWidgetActivityContent(
+        activity: ComponentActivity,
+        viewModel: BaseCommunalViewModel,
+        onOpenWidgetPicker: () -> Unit,
+    ) {
+        throwComposeUnavailableError()
+    }
+
     override fun createFooterActionsView(
         context: Context,
         viewModel: FooterActionsViewModel,
@@ -67,12 +75,12 @@
 
     override fun createCommunalView(
         context: Context,
-        viewModel: CommunalViewModel,
+        viewModel: BaseCommunalViewModel,
     ): View {
         throwComposeUnavailableError()
     }
 
-    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
         throwComposeUnavailableError()
     }
 
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index eeda6c6..59bd95b 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -62,6 +62,21 @@
         activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
     }
 
+    override fun setCommunalEditWidgetActivityContent(
+        activity: ComponentActivity,
+        viewModel: BaseCommunalViewModel,
+        onOpenWidgetPicker: () -> Unit,
+    ) {
+        activity.setContent {
+            PlatformTheme {
+                CommunalHub(
+                    viewModel = viewModel,
+                    onOpenWidgetPicker = onOpenWidgetPicker,
+                )
+            }
+        }
+    }
+
     override fun createFooterActionsView(
         context: Context,
         viewModel: FooterActionsViewModel,
@@ -98,14 +113,14 @@
 
     override fun createCommunalView(
         context: Context,
-        viewModel: CommunalViewModel,
+        viewModel: BaseCommunalViewModel,
     ): View {
         return ComposeView(context).apply {
             setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
         }
     }
 
-    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
         return ComposeView(context).apply {
             setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
         }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
index ddc3d3a..1860c9f 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
@@ -18,8 +18,8 @@
 
 import android.app.AlertDialog
 import android.content.Context
+import com.android.systemui.bouncer.ui.composable.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.bouncer.ui.composable.BouncerSceneDialogFactory
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.Scene
@@ -38,8 +38,8 @@
 
         @Provides
         @SysUISingleton
-        fun bouncerSceneDialogFactory(@Application context: Context): BouncerSceneDialogFactory {
-            return object : BouncerSceneDialogFactory {
+        fun bouncerSceneDialogFactory(@Application context: Context): BouncerDialogFactory {
+            return object : BouncerDialogFactory {
                 override fun invoke(): AlertDialog {
                     return SystemUIDialog(context)
                 }
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 16c2437..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,7 +31,6 @@
     ],
 
     static_libs: [
-        "CommunalLayoutLib",
         "SystemUI-core",
         "PlatformComposeCore",
         "PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
new file mode 100644
index 0000000..ba80a8d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -0,0 +1,734 @@
+/*
+ * 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.bouncer.ui.composable
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.DialogInterface
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Arrangement
+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.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+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.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+import com.android.compose.PlatformButton
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
+import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.fold.ui.composable.foldPosture
+import com.android.systemui.fold.ui.helper.FoldPosture
+import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.pow
+
+@Composable
+fun BouncerContent(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    modifier: Modifier
+) {
+    val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
+    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
+    val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
+
+    when (layout) {
+        BouncerSceneLayout.STANDARD ->
+            StandardLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                modifier = modifier,
+            )
+        BouncerSceneLayout.SIDE_BY_SIDE ->
+            SideBySideLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
+                modifier = modifier,
+            )
+        BouncerSceneLayout.STACKED ->
+            StackedLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
+                modifier = modifier,
+            )
+        BouncerSceneLayout.SPLIT ->
+            SplitLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                modifier = modifier,
+            )
+    }
+}
+
+/**
+ * Renders the contents of the actual bouncer UI, the area that takes user input to do an
+ * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.).
+ */
+@Composable
+private fun StandardLayout(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    modifier: Modifier = Modifier,
+    outputOnly: Boolean = false,
+) {
+    val foldPosture: FoldPosture by foldPosture()
+    val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
+    val isSplitAroundTheFold =
+        foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+    val currentSceneKey =
+        if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
+
+    SceneTransitionLayout(
+        currentScene = currentSceneKey,
+        onChangeScene = {},
+        transitions = SceneTransitions,
+        modifier = modifier,
+    ) {
+        scene(SceneKeys.ContiguousSceneKey) {
+            FoldSplittable(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                outputOnly = outputOnly,
+                isSplit = false,
+            )
+        }
+
+        scene(SceneKeys.SplitSceneKey) {
+            FoldSplittable(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                outputOnly = outputOnly,
+                isSplit = true,
+            )
+        }
+    }
+}
+
+/**
+ * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
+ * switcher UI) and laid out vertically, centered horizontally.
+ *
+ * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
+ * render across the location of the fold hardware when the device is fully or part-way unfolded
+ * with the fold hinge in a horizontal position.
+ *
+ * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
+ * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
+ * their PIN or pattern.
+ */
+@Composable
+private fun SceneScope.FoldSplittable(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    outputOnly: Boolean,
+    isSplit: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+    val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
+    var dialog: Dialog? by remember { mutableStateOf(null) }
+    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+    val splitRatio =
+        LocalContext.current.resources.getFloat(
+            R.dimen.motion_layout_half_fold_bouncer_height_ratio
+        )
+
+    Column(modifier = modifier.padding(horizontal = 32.dp)) {
+        // Content above the fold, when split on a foldable device in a "table top" posture:
+        Box(
+            modifier =
+                Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) {
+                    Modifier.weight(splitRatio)
+                },
+        ) {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth().padding(top = 92.dp),
+            ) {
+                Crossfade(
+                    targetState = message,
+                    label = "Bouncer message",
+                    animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+                ) { message ->
+                    Text(
+                        text = message.text,
+                        color = MaterialTheme.colorScheme.onSurface,
+                        style = MaterialTheme.typography.bodyLarge,
+                    )
+                }
+
+                Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
+
+                UserInputArea(
+                    viewModel = viewModel,
+                    visibility = UserInputAreaVisibility.OUTPUT_ONLY,
+                )
+            }
+        }
+
+        // Content below the fold, when split on a foldable device in a "table top" posture:
+        Box(
+            modifier =
+                Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) {
+                    Modifier.weight(1 - splitRatio)
+                },
+        ) {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                if (!outputOnly) {
+                    Box(Modifier.weight(1f)) {
+                        UserInputArea(
+                            viewModel = viewModel,
+                            visibility = UserInputAreaVisibility.INPUT_ONLY,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    }
+                }
+
+                Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
+
+                val actionButtonModifier = Modifier.height(56.dp)
+
+                actionButton.let { actionButtonViewModel ->
+                    if (actionButtonViewModel != null) {
+                        BouncerActionButton(
+                            viewModel = actionButtonViewModel,
+                            modifier = actionButtonModifier,
+                        )
+                    } else {
+                        Spacer(modifier = actionButtonModifier)
+                    }
+                }
+
+                Spacer(Modifier.height(48.dp))
+            }
+        }
+
+        if (dialogMessage != null) {
+            if (dialog == null) {
+                dialog =
+                    dialogFactory().apply {
+                        setMessage(dialogMessage)
+                        setButton(
+                            DialogInterface.BUTTON_NEUTRAL,
+                            context.getString(R.string.ok),
+                        ) { _, _ ->
+                            viewModel.onThrottlingDialogDismissed()
+                        }
+                        setCancelable(false)
+                        setCanceledOnTouchOutside(false)
+                        show()
+                    }
+            }
+        } else {
+            dialog?.dismiss()
+            dialog = null
+        }
+    }
+}
+
+/**
+ * Renders the user input area, where the user interacts with the UI to enter their credentials.
+ *
+ * For example, this can be the pattern input area, the password text box, or pin pad.
+ */
+@Composable
+private fun UserInputArea(
+    viewModel: BouncerViewModel,
+    visibility: UserInputAreaVisibility,
+    modifier: Modifier = Modifier,
+) {
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
+
+    when (val nonNullViewModel = authMethodViewModel) {
+        is PinBouncerViewModel ->
+            when (visibility) {
+                UserInputAreaVisibility.OUTPUT_ONLY ->
+                    PinInputDisplay(
+                        viewModel = nonNullViewModel,
+                        modifier = modifier,
+                    )
+                UserInputAreaVisibility.INPUT_ONLY ->
+                    PinPad(
+                        viewModel = nonNullViewModel,
+                        modifier = modifier,
+                    )
+            }
+        is PasswordBouncerViewModel ->
+            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
+                PasswordBouncer(
+                    viewModel = nonNullViewModel,
+                    modifier = modifier,
+                )
+            }
+        is PatternBouncerViewModel ->
+            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
+                PatternBouncer(
+                    viewModel = nonNullViewModel,
+                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+                )
+            }
+        else -> Unit
+    }
+}
+
+/**
+ * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun BouncerActionButton(
+    viewModel: BouncerActionButtonModel,
+    modifier: Modifier = Modifier,
+) {
+    Button(
+        onClick = viewModel.onClick,
+        modifier =
+            modifier.thenIf(viewModel.onLongClick != null) {
+                Modifier.combinedClickable(
+                    onClick = viewModel.onClick,
+                    onLongClick = viewModel.onLongClick,
+                )
+            },
+        colors =
+            ButtonDefaults.buttonColors(
+                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+            ),
+    ) {
+        Text(
+            text = viewModel.label,
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
+}
+
+/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
+@Composable
+private fun UserSwitcher(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
+    val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center,
+        modifier = modifier,
+    ) {
+        selectedUserImage?.let {
+            Image(
+                bitmap = it.asImageBitmap(),
+                contentDescription = null,
+                modifier = Modifier.size(SelectedUserImageSize),
+            )
+        }
+
+        val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) }
+
+        dropdownItems.firstOrNull()?.let { firstDropdownItem ->
+            Spacer(modifier = Modifier.height(40.dp))
+
+            Box {
+                PlatformButton(
+                    modifier =
+                        Modifier
+                            // Remove the built-in padding applied inside PlatformButton:
+                            .padding(vertical = 0.dp)
+                            .width(UserSwitcherDropdownWidth)
+                            .height(UserSwitcherDropdownHeight),
+                    colors =
+                        ButtonDefaults.buttonColors(
+                            containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+                            contentColor = MaterialTheme.colorScheme.onSurface,
+                        ),
+                    onClick = { setDropdownExpanded(!isDropdownExpanded) },
+                ) {
+                    val context = LocalContext.current
+                    Text(
+                        text = checkNotNull(firstDropdownItem.text.loadText(context)),
+                        style = MaterialTheme.typography.headlineSmall,
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis,
+                    )
+
+                    Spacer(modifier = Modifier.weight(1f))
+
+                    Icon(
+                        imageVector = Icons.Default.KeyboardArrowDown,
+                        contentDescription = null,
+                        modifier = Modifier.size(32.dp),
+                    )
+                }
+
+                UserSwitcherDropdownMenu(
+                    isExpanded = isDropdownExpanded,
+                    items = dropdownItems,
+                    onDismissed = { setDropdownExpanded(false) },
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Renders the dropdown menu that displays the actual users and/or user actions that can be
+ * selected.
+ */
+@Composable
+private fun UserSwitcherDropdownMenu(
+    isExpanded: Boolean,
+    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+    onDismissed: () -> Unit,
+) {
+    val context = LocalContext.current
+
+    // TODO(b/303071855): once the FR is fixed, remove this composition local override.
+    MaterialTheme(
+        colorScheme =
+            MaterialTheme.colorScheme.copy(
+                surface = MaterialTheme.colorScheme.surfaceContainerHighest,
+            ),
+        shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)),
+    ) {
+        DropdownMenu(
+            expanded = isExpanded,
+            onDismissRequest = onDismissed,
+            offset =
+                DpOffset(
+                    x = 0.dp,
+                    y = -UserSwitcherDropdownHeight,
+                ),
+            modifier = Modifier.width(UserSwitcherDropdownWidth),
+        ) {
+            items.forEach { userSwitcherDropdownItem ->
+                DropdownMenuItem(
+                    leadingIcon = {
+                        Icon(
+                            icon = userSwitcherDropdownItem.icon,
+                            tint = MaterialTheme.colorScheme.primary,
+                            modifier = Modifier.size(28.dp),
+                        )
+                    },
+                    text = {
+                        Text(
+                            text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)),
+                            style = MaterialTheme.typography.bodyLarge,
+                            color = MaterialTheme.colorScheme.onSurface,
+                        )
+                    },
+                    onClick = {
+                        onDismissed()
+                        userSwitcherDropdownItem.onClick()
+                    },
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
+ * by double-tapping on the side.
+ */
+@Composable
+private fun SplitLayout(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    modifier: Modifier = Modifier,
+) {
+    SwappableLayout(
+        startContent = { startContentModifier ->
+            StandardLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                outputOnly = true,
+                modifier = startContentModifier,
+            )
+        },
+        endContent = { endContentModifier ->
+            UserInputArea(
+                viewModel = viewModel,
+                visibility = UserInputAreaVisibility.INPUT_ONLY,
+                modifier = endContentModifier,
+            )
+        },
+        modifier = modifier
+    )
+}
+
+/**
+ * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
+ * to flip their positions.
+ */
+@Composable
+private fun SwappableLayout(
+    startContent: @Composable (Modifier) -> Unit,
+    endContent: @Composable (Modifier) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+
+    Row(
+        modifier =
+            modifier.pointerInput(Unit) {
+                detectTapGestures(
+                    onDoubleTap = { offset ->
+                        // Depending on where the user double tapped, switch the elements such that
+                        // the endContent is closer to the side that was double tapped.
+                        setSwapped(offset.x < size.width / 2)
+                    }
+                )
+            },
+    ) {
+        val animatedOffset by
+            animateFloatAsState(
+                targetValue =
+                    if (!isSwapped) {
+                        // When startContent is first, both elements have their natural placement so
+                        // they are not offset in any way.
+                        0f
+                    } else if (isLeftToRight) {
+                        // Since startContent is not first, the elements have to be swapped
+                        // horizontally. In the case of LTR locales, this means pushing startContent
+                        // to the right, hence the positive number.
+                        1f
+                    } else {
+                        // Since startContent is not first, the elements have to be swapped
+                        // horizontally. In the case of RTL locales, this means pushing startContent
+                        // to the left, hence the negative number.
+                        -1f
+                    },
+                label = "offset",
+            )
+
+        startContent(
+            Modifier.fillMaxHeight().weight(1f).graphicsLayer {
+                translationX = size.width * animatedOffset
+                alpha = animatedAlpha(animatedOffset)
+            }
+        )
+
+        Box(
+            modifier =
+                Modifier.fillMaxHeight().weight(1f).graphicsLayer {
+                    // A negative sign is used to make sure this is offset in the direction that's
+                    // opposite of the direction that the user switcher is pushed in.
+                    translationX = -size.width * animatedOffset
+                    alpha = animatedAlpha(animatedOffset)
+                }
+        ) {
+            endContent(Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter))
+        }
+    }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ *
+ * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
+ * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
+ * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
+ * rendering of the bouncer will be used instead of the side-by-side layout.
+ */
+@Composable
+private fun SideBySideLayout(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    isUserSwitcherVisible: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    SwappableLayout(
+        startContent = { startContentModifier ->
+            if (isUserSwitcherVisible) {
+                UserSwitcher(
+                    viewModel = viewModel,
+                    modifier = startContentModifier,
+                )
+            } else {
+                Box(
+                    modifier = startContentModifier,
+                )
+            }
+        },
+        endContent = { endContentModifier ->
+            StandardLayout(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                modifier = endContentModifier,
+            )
+        },
+        modifier = modifier,
+    )
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
+@Composable
+private fun StackedLayout(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+    isUserSwitcherVisible: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier,
+    ) {
+        if (isUserSwitcherVisible) {
+            UserSwitcher(
+                viewModel = viewModel,
+                modifier = Modifier.fillMaxWidth().weight(1f),
+            )
+        }
+
+        StandardLayout(
+            viewModel = viewModel,
+            dialogFactory = dialogFactory,
+            modifier = Modifier.fillMaxWidth().weight(1f),
+        )
+    }
+}
+
+interface BouncerDialogFactory {
+    operator fun invoke(): AlertDialog
+}
+
+/** Enumerates all supported user-input area visibilities. */
+private enum class UserInputAreaVisibility {
+    /**
+     * Only the area where the user enters the input is shown; the area where the input is reflected
+     * back to the user is not shown.
+     */
+    INPUT_ONLY,
+    /**
+     * Only the area where the input is reflected back to the user is shown; the area where the
+     * input is entered by the user is not shown.
+     */
+    OUTPUT_ONLY,
+}
+
+/**
+ * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
+ * the two reaches a stopping point but `0` in the middle of the transition.
+ */
+private fun animatedAlpha(
+    offset: Float,
+): Float {
+    // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around
+    // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs
+    // between x = 0 and x = 1.
+    //
+    // The minimum values of the curves are at -0.5 and +0.5.
+    //
+    // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1).
+    //
+    // Breaking it down, it's y = a×(|x|-m)²+b, where:
+    // x: the offset
+    // y: the alpha
+    // m: x-axis center of the parabolic curves, where the minima are.
+    // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha =
+    // 0.
+    // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1.
+    val m = 0.5f
+    val b = -0.25
+    val a = (1 - b) / m.pow(2)
+
+    return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat())
+}
+
+private val SelectedUserImageSize = 190.dp
+private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp
+private val UserSwitcherDropdownHeight = 60.dp
+
+private object SceneKeys {
+    val ContiguousSceneKey = SceneKey("default")
+    val SplitSceneKey = SceneKey("split")
+}
+
+private object SceneElements {
+    val AboveFold = ElementKey("above_fold")
+    val BelowFold = ElementKey("below_fold")
+}
+
+private val SceneTransitions = transitions {
+    from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 7eb7dac..d638ffe 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -16,95 +16,22 @@
 
 package com.android.systemui.bouncer.ui.composable
 
-import android.app.AlertDialog
-import android.app.Dialog
-import android.content.DialogInterface
-import android.content.res.Configuration
-import androidx.compose.animation.Crossfade
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.Arrangement
 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.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.KeyboardArrowDown
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
-import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 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.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.DpOffset
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.times
-import com.android.compose.PlatformButton
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.SceneKey as SceneTransitionLayoutSceneKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.transitions
-import com.android.compose.modifiers.thenIf
-import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
-import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
-import com.android.systemui.common.shared.model.Text.Companion.loadText
-import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.fold.ui.composable.FoldPosture
-import com.android.systemui.fold.ui.composable.foldPosture
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.pow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -122,7 +49,7 @@
 @Inject
 constructor(
     private val viewModel: BouncerViewModel,
-    private val dialogFactory: BouncerSceneDialogFactory,
+    private val dialogFactory: BouncerDialogFactory,
 ) : ComposableScene {
     override val key = SceneKey.Bouncer
 
@@ -144,699 +71,22 @@
 @Composable
 private fun SceneScope.BouncerScene(
     viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
+    dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
     val backgroundColor = MaterialTheme.colorScheme.surface
-    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
-    val layout =
-        calculateLayout(
-            isSideBySideSupported = isSideBySideSupported,
-        )
 
     Box(modifier) {
         Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
             drawRect(color = backgroundColor)
         }
 
-        val childModifier = Modifier.element(Bouncer.Elements.Content).fillMaxSize()
-        val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
-
-        when (layout) {
-            Layout.STANDARD ->
-                StandardLayout(
-                    viewModel = viewModel,
-                    dialogFactory = dialogFactory,
-                    modifier = childModifier,
-                )
-            Layout.SIDE_BY_SIDE ->
-                SideBySideLayout(
-                    viewModel = viewModel,
-                    dialogFactory = dialogFactory,
-                    isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                    modifier = childModifier,
-                )
-            Layout.STACKED ->
-                StackedLayout(
-                    viewModel = viewModel,
-                    dialogFactory = dialogFactory,
-                    isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                    modifier = childModifier,
-                )
-            Layout.SPLIT ->
-                SplitLayout(
-                    viewModel = viewModel,
-                    dialogFactory = dialogFactory,
-                    modifier = childModifier,
-                )
-        }
-    }
-}
-
-/**
- * Renders the contents of the actual bouncer UI, the area that takes user input to do an
- * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.).
- */
-@Composable
-private fun StandardLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
-    modifier: Modifier = Modifier,
-    outputOnly: Boolean = false,
-) {
-    val foldPosture: FoldPosture by foldPosture()
-    val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
-    val isSplitAroundTheFold =
-        foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
-    val currentSceneKey =
-        if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
-
-    SceneTransitionLayout(
-        currentScene = currentSceneKey,
-        onChangeScene = {},
-        transitions = SceneTransitions,
-        modifier = modifier,
-    ) {
-        scene(SceneKeys.ContiguousSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                outputOnly = outputOnly,
-                isSplit = false,
-            )
-        }
-
-        scene(SceneKeys.SplitSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                outputOnly = outputOnly,
-                isSplit = true,
-            )
-        }
-    }
-}
-
-/**
- * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
- * switcher UI) and laid out vertically, centered horizontally.
- *
- * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
- * render across the location of the fold hardware when the device is fully or part-way unfolded
- * with the fold hinge in a horizontal position.
- *
- * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
- * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
- * their PIN or pattern.
- */
-@Composable
-private fun SceneScope.FoldSplittable(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
-    outputOnly: Boolean,
-    isSplit: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
-    var dialog: Dialog? by remember { mutableStateOf(null) }
-    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
-    val splitRatio =
-        LocalContext.current.resources.getFloat(
-            R.dimen.motion_layout_half_fold_bouncer_height_ratio
-        )
-
-    Column(modifier = modifier.padding(horizontal = 32.dp)) {
-        // Content above the fold, when split on a foldable device in a "table top" posture:
-        Box(
-            modifier =
-                Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) {
-                    Modifier.weight(splitRatio)
-                },
-        ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth().padding(top = 92.dp),
-            ) {
-                Crossfade(
-                    targetState = message,
-                    label = "Bouncer message",
-                    animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-                ) { message ->
-                    Text(
-                        text = message.text,
-                        color = MaterialTheme.colorScheme.onSurface,
-                        style = MaterialTheme.typography.bodyLarge,
-                    )
-                }
-
-                Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
-
-                UserInputArea(
-                    viewModel = viewModel,
-                    visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                )
-            }
-        }
-
-        // Content below the fold, when split on a foldable device in a "table top" posture:
-        Box(
-            modifier =
-                Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) {
-                    Modifier.weight(1 - splitRatio)
-                },
-        ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth(),
-            ) {
-                if (!outputOnly) {
-                    Box(Modifier.weight(1f)) {
-                        UserInputArea(
-                            viewModel = viewModel,
-                            visibility = UserInputAreaVisibility.INPUT_ONLY,
-                            modifier = Modifier.align(Alignment.Center),
-                        )
-                    }
-                }
-
-                Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
-
-                val actionButtonModifier = Modifier.height(56.dp)
-
-                actionButton.let { actionButtonViewModel ->
-                    if (actionButtonViewModel != null) {
-                        BouncerActionButton(
-                            viewModel = actionButtonViewModel,
-                            modifier = actionButtonModifier,
-                        )
-                    } else {
-                        Spacer(modifier = actionButtonModifier)
-                    }
-                }
-
-                Spacer(Modifier.height(48.dp))
-            }
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
-            }
-        } else {
-            dialog?.dismiss()
-            dialog = null
-        }
-    }
-}
-
-/**
- * Renders the user input area, where the user interacts with the UI to enter their credentials.
- *
- * For example, this can be the pattern input area, the password text box, or pin pad.
- */
-@Composable
-private fun UserInputArea(
-    viewModel: BouncerViewModel,
-    visibility: UserInputAreaVisibility,
-    modifier: Modifier = Modifier,
-) {
-    val authMethodViewModel: AuthMethodBouncerViewModel? by
-        viewModel.authMethodViewModel.collectAsState()
-
-    when (val nonNullViewModel = authMethodViewModel) {
-        is PinBouncerViewModel ->
-            when (visibility) {
-                UserInputAreaVisibility.OUTPUT_ONLY ->
-                    PinInputDisplay(
-                        viewModel = nonNullViewModel,
-                        modifier = modifier,
-                    )
-                UserInputAreaVisibility.INPUT_ONLY ->
-                    PinPad(
-                        viewModel = nonNullViewModel,
-                        modifier = modifier,
-                    )
-            }
-        is PasswordBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PasswordBouncer(
-                    viewModel = nonNullViewModel,
-                    modifier = modifier,
-                )
-            }
-        is PatternBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PatternBouncer(
-                    viewModel = nonNullViewModel,
-                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
-                )
-            }
-        else -> Unit
-    }
-}
-
-/**
- * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
- */
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-private fun BouncerActionButton(
-    viewModel: BouncerActionButtonModel,
-    modifier: Modifier = Modifier,
-) {
-    Button(
-        onClick = viewModel.onClick,
-        modifier =
-            modifier.thenIf(viewModel.onLongClick != null) {
-                Modifier.combinedClickable(
-                    onClick = viewModel.onClick,
-                    onLongClick = viewModel.onLongClick,
-                )
-            },
-        colors =
-            ButtonDefaults.buttonColors(
-                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-            ),
-    ) {
-        Text(
-            text = viewModel.label,
-            style = MaterialTheme.typography.bodyMedium,
+        // Separate the bouncer content into a reusable composable that doesn't have any SceneScope
+        // dependencies
+        BouncerContent(
+            viewModel,
+            dialogFactory,
+            Modifier.element(Bouncer.Elements.Content).fillMaxSize()
         )
     }
 }
-
-/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
-@Composable
-private fun UserSwitcher(
-    viewModel: BouncerViewModel,
-    modifier: Modifier = Modifier,
-) {
-    val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
-    val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.Center,
-        modifier = modifier,
-    ) {
-        selectedUserImage?.let {
-            Image(
-                bitmap = it.asImageBitmap(),
-                contentDescription = null,
-                modifier = Modifier.size(SelectedUserImageSize),
-            )
-        }
-
-        val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) }
-
-        dropdownItems.firstOrNull()?.let { firstDropdownItem ->
-            Spacer(modifier = Modifier.height(40.dp))
-
-            Box {
-                PlatformButton(
-                    modifier =
-                        Modifier
-                            // Remove the built-in padding applied inside PlatformButton:
-                            .padding(vertical = 0.dp)
-                            .width(UserSwitcherDropdownWidth)
-                            .height(UserSwitcherDropdownHeight),
-                    colors =
-                        ButtonDefaults.buttonColors(
-                            containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
-                            contentColor = MaterialTheme.colorScheme.onSurface,
-                        ),
-                    onClick = { setDropdownExpanded(!isDropdownExpanded) },
-                ) {
-                    val context = LocalContext.current
-                    Text(
-                        text = checkNotNull(firstDropdownItem.text.loadText(context)),
-                        style = MaterialTheme.typography.headlineSmall,
-                        maxLines = 1,
-                        overflow = TextOverflow.Ellipsis,
-                    )
-
-                    Spacer(modifier = Modifier.weight(1f))
-
-                    Icon(
-                        imageVector = Icons.Default.KeyboardArrowDown,
-                        contentDescription = null,
-                        modifier = Modifier.size(32.dp),
-                    )
-                }
-
-                UserSwitcherDropdownMenu(
-                    isExpanded = isDropdownExpanded,
-                    items = dropdownItems,
-                    onDismissed = { setDropdownExpanded(false) },
-                )
-            }
-        }
-    }
-}
-
-/**
- * Renders the dropdowm menu that displays the actual users and/or user actions that can be
- * selected.
- */
-@Composable
-private fun UserSwitcherDropdownMenu(
-    isExpanded: Boolean,
-    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
-    onDismissed: () -> Unit,
-) {
-    val context = LocalContext.current
-
-    // TODO(b/303071855): once the FR is fixed, remove this composition local override.
-    MaterialTheme(
-        colorScheme =
-            MaterialTheme.colorScheme.copy(
-                surface = MaterialTheme.colorScheme.surfaceContainerHighest,
-            ),
-        shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)),
-    ) {
-        DropdownMenu(
-            expanded = isExpanded,
-            onDismissRequest = onDismissed,
-            offset =
-                DpOffset(
-                    x = 0.dp,
-                    y = -UserSwitcherDropdownHeight,
-                ),
-            modifier = Modifier.width(UserSwitcherDropdownWidth),
-        ) {
-            items.forEach { userSwitcherDropdownItem ->
-                DropdownMenuItem(
-                    leadingIcon = {
-                        Icon(
-                            icon = userSwitcherDropdownItem.icon,
-                            tint = MaterialTheme.colorScheme.primary,
-                            modifier = Modifier.size(28.dp),
-                        )
-                    },
-                    text = {
-                        Text(
-                            text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)),
-                            style = MaterialTheme.typography.bodyLarge,
-                            color = MaterialTheme.colorScheme.onSurface,
-                        )
-                    },
-                    onClick = {
-                        onDismissed()
-                        userSwitcherDropdownItem.onClick()
-                    },
-                )
-            }
-        }
-    }
-}
-
-/**
- * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
- * by double-tapping on the side.
- */
-@Composable
-private fun SplitLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                outputOnly = true,
-                modifier = startContentModifier,
-            )
-        },
-        endContent = { endContentModifier ->
-            UserInputArea(
-                viewModel = viewModel,
-                visibility = UserInputAreaVisibility.INPUT_ONLY,
-                modifier = endContentModifier,
-            )
-        },
-        modifier = modifier
-    )
-}
-
-/**
- * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
- * to flip their positions.
- */
-@Composable
-private fun SwappableLayout(
-    startContent: @Composable (Modifier) -> Unit,
-    endContent: @Composable (Modifier) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val layoutDirection = LocalLayoutDirection.current
-    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
-    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
-
-    Row(
-        modifier =
-            modifier.pointerInput(Unit) {
-                detectTapGestures(
-                    onDoubleTap = { offset ->
-                        // Depending on where the user double tapped, switch the elements such that
-                        // the endContent is closer to the side that was double tapped.
-                        setSwapped(offset.x < size.width / 2)
-                    }
-                )
-            },
-    ) {
-        val animatedOffset by
-            animateFloatAsState(
-                targetValue =
-                    if (!isSwapped) {
-                        // When startContent is first, both elements have their natural placement so
-                        // they are not offset in any way.
-                        0f
-                    } else if (isLeftToRight) {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of LTR locales, this means pushing startContent
-                        // to the right, hence the positive number.
-                        1f
-                    } else {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of RTL locales, this means pushing startContent
-                        // to the left, hence the negative number.
-                        -1f
-                    },
-                label = "offset",
-            )
-
-        startContent(
-            Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                translationX = size.width * animatedOffset
-                alpha = animatedAlpha(animatedOffset)
-            }
-        )
-
-        Box(
-            modifier =
-                Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                    // A negative sign is used to make sure this is offset in the direction that's
-                    // opposite of the direction that the user switcher is pushed in.
-                    translationX = -size.width * animatedOffset
-                    alpha = animatedAlpha(animatedOffset)
-                }
-        ) {
-            endContent(Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter))
-        }
-    }
-}
-
-/**
- * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
- * anywhere on the background to flip their positions.
- *
- * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
- * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
- * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
- * rendering of the bouncer will be used instead of the side-by-side layout.
- */
-@Composable
-private fun SideBySideLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            if (isUserSwitcherVisible) {
-                UserSwitcher(
-                    viewModel = viewModel,
-                    modifier = startContentModifier,
-                )
-            } else {
-                Box(
-                    modifier = startContentModifier,
-                )
-            }
-        },
-        endContent = { endContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = endContentModifier,
-            )
-        },
-        modifier = modifier,
-    )
-}
-
-/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
-@Composable
-private fun StackedLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerSceneDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        modifier = modifier,
-    ) {
-        if (isUserSwitcherVisible) {
-            UserSwitcher(
-                viewModel = viewModel,
-                modifier = Modifier.fillMaxWidth().weight(1f),
-            )
-        }
-
-        StandardLayout(
-            viewModel = viewModel,
-            dialogFactory = dialogFactory,
-            modifier = Modifier.fillMaxWidth().weight(1f),
-        )
-    }
-}
-
-@Composable
-private fun calculateLayout(
-    isSideBySideSupported: Boolean,
-): Layout {
-    val windowSizeClass = LocalWindowSizeClass.current
-    val width = windowSizeClass.widthSizeClass
-    val height = windowSizeClass.heightSizeClass
-    val isLarge = width > WindowWidthSizeClass.Compact && height > WindowHeightSizeClass.Compact
-    val isTall =
-        when (height) {
-            WindowHeightSizeClass.Expanded -> width < WindowWidthSizeClass.Expanded
-            WindowHeightSizeClass.Medium -> width < WindowWidthSizeClass.Medium
-            else -> false
-        }
-    val isSquare =
-        when (width) {
-            WindowWidthSizeClass.Compact -> height == WindowHeightSizeClass.Compact
-            WindowWidthSizeClass.Medium -> height == WindowHeightSizeClass.Medium
-            WindowWidthSizeClass.Expanded -> height == WindowHeightSizeClass.Expanded
-            else -> false
-        }
-    val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
-
-    return when {
-        // Small and tall devices (i.e. phone/folded in portrait) or square device not in landscape
-        // mode (unfolded with hinge along horizontal plane).
-        (!isLarge && isTall) || (isSquare && !isLandscape) -> Layout.STANDARD
-        // Small and wide devices (i.e. phone/folded in landscape).
-        !isLarge -> Layout.SPLIT
-        // Large and tall devices (i.e. tablet in portrait).
-        isTall -> Layout.STACKED
-        // Large and wide/square devices (i.e. tablet in landscape, unfolded).
-        else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD
-    }
-}
-
-interface BouncerSceneDialogFactory {
-    operator fun invoke(): AlertDialog
-}
-
-/** Enumerates all known adaptive layout configurations. */
-private enum class Layout {
-    /** The default UI with the bouncer laid out normally. */
-    STANDARD,
-    /** The bouncer is displayed vertically stacked with the user switcher. */
-    STACKED,
-    /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
-    SIDE_BY_SIDE,
-    /** The bouncer is split in two with both sides shown side-by-side. */
-    SPLIT,
-}
-
-/** Enumerates all supported user-input area visibilities. */
-private enum class UserInputAreaVisibility {
-    /**
-     * Only the area where the user enters the input is shown; the area where the input is reflected
-     * back to the user is not shown.
-     */
-    INPUT_ONLY,
-    /**
-     * Only the area where the input is reflected back to the user is shown; the area where the
-     * input is entered by the user is not shown.
-     */
-    OUTPUT_ONLY,
-}
-
-/**
- * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
- * the two reaches a stopping point but `0` in the middle of the transition.
- */
-private fun animatedAlpha(
-    offset: Float,
-): Float {
-    // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around
-    // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs
-    // between x = 0 and x = 1.
-    //
-    // The minimum values of the curves are at -0.5 and +0.5.
-    //
-    // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1).
-    //
-    // Breaking it down, it's y = a×(|x|-m)²+b, where:
-    // x: the offset
-    // y: the alpha
-    // m: x-axis center of the parabolic curves, where the minima are.
-    // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha =
-    // 0.
-    // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1.
-    val m = 0.5f
-    val b = -0.25
-    val a = (1 - b) / m.pow(2)
-
-    return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat())
-}
-
-private val SelectedUserImageSize = 190.dp
-private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp
-private val UserSwitcherDropdownHeight = 60.dp
-
-private object SceneKeys {
-    val ContiguousSceneKey = SceneTransitionLayoutSceneKey("default")
-    val SplitSceneKey = SceneTransitionLayoutSceneKey("split")
-}
-
-private object SceneElements {
-    val AboveFold = ElementKey("above_fold")
-    val BelowFold = ElementKey("below_fold")
-}
-
-private val SceneTransitions = transitions {
-    from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
new file mode 100644
index 0000000..08b7559
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.bouncer.ui.composable
+
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.runtime.Composable
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
+import com.android.systemui.bouncer.ui.helper.SizeClass
+import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal
+
+/**
+ * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
+ * [BouncerSceneLayout.STANDARD].
+ */
+@Composable
+fun calculateLayout(
+    isSideBySideSupported: Boolean,
+): BouncerSceneLayout {
+    val windowSizeClass = LocalWindowSizeClass.current
+
+    return calculateLayoutInternal(
+        width = windowSizeClass.widthSizeClass.toEnum(),
+        height = windowSizeClass.heightSizeClass.toEnum(),
+        isSideBySideSupported = isSideBySideSupported,
+    )
+}
+
+private fun WindowWidthSizeClass.toEnum(): SizeClass {
+    return when (this) {
+        WindowWidthSizeClass.Compact -> SizeClass.COMPACT
+        WindowWidthSizeClass.Medium -> SizeClass.MEDIUM
+        WindowWidthSizeClass.Expanded -> SizeClass.EXPANDED
+        else -> error("Unsupported WindowWidthSizeClass \"$this\"")
+    }
+}
+
+private fun WindowHeightSizeClass.toEnum(): SizeClass {
+    return when (this) {
+        WindowHeightSizeClass.Compact -> SizeClass.COMPACT
+        WindowHeightSizeClass.Medium -> SizeClass.MEDIUM
+        WindowHeightSizeClass.Expanded -> SizeClass.EXPANDED
+        else -> error("Unsupported WindowHeightSizeClass \"$this\"")
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 2bbe9b8..ff1cbd6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -39,11 +39,11 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.res.integerResource
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
 import com.android.compose.modifiers.thenIf
@@ -79,12 +79,6 @@
     val lineColor = MaterialTheme.colorScheme.primary
     val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() }
 
-    var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
-    val horizontalSpacing = containerSize.width / colCount
-    val verticalSpacing = containerSize.height / rowCount
-    val spacing = min(horizontalSpacing, verticalSpacing).toFloat()
-    val verticalOffset = containerSize.height - spacing * rowCount
-
     // All dots that should be rendered on the grid.
     val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
     // The most recently selected dot, if the user is currently dragging.
@@ -195,13 +189,14 @@
 
     // This is the position of the input pointer.
     var inputPosition: Offset? by remember { mutableStateOf(null) }
+    var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
 
     Canvas(
         modifier
             // Need to clip to bounds to make sure that the lines don't follow the input pointer
             // when it leaves the bounds of the dot grid.
             .clipToBounds()
-            .onSizeChanged { containerSize = it }
+            .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
             .thenIf(isInputEnabled) {
                 Modifier.pointerInput(Unit) {
                         awaitEachGesture {
@@ -232,63 +227,73 @@
                             viewModel.onDrag(
                                 xPx = change.position.x,
                                 yPx = change.position.y,
-                                containerSizePx = containerSize.width,
-                                verticalOffsetPx = verticalOffset,
+                                containerSizePx = size.width,
                             )
                         }
                     }
             }
     ) {
-        if (isAnimationEnabled) {
-            // Draw lines between dots.
-            selectedDots.forEachIndexed { index, dot ->
-                if (index > 0) {
-                    val previousDot = selectedDots[index - 1]
-                    val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
-                    val startLerp = 1 - lineFadeOutAnimationProgress
-                    val from = pixelOffset(previousDot, spacing, verticalOffset)
-                    val to = pixelOffset(dot, spacing, verticalOffset)
-                    val lerpedFrom =
-                        Offset(
-                            x = from.x + (to.x - from.x) * startLerp,
-                            y = from.y + (to.y - from.y) * startLerp,
+        gridCoordinates?.let { nonNullCoordinates ->
+            val containerSize = nonNullCoordinates.size
+            val horizontalSpacing = containerSize.width.toFloat() / colCount
+            val verticalSpacing = containerSize.height.toFloat() / rowCount
+            val spacing = min(horizontalSpacing, verticalSpacing)
+            val verticalOffset = containerSize.height - spacing * rowCount
+
+            if (isAnimationEnabled) {
+                // Draw lines between dots.
+                selectedDots.forEachIndexed { index, dot ->
+                    if (index > 0) {
+                        val previousDot = selectedDots[index - 1]
+                        val lineFadeOutAnimationProgress =
+                            lineFadeOutAnimatables[previousDot]!!.value
+                        val startLerp = 1 - lineFadeOutAnimationProgress
+                        val from = pixelOffset(previousDot, spacing, verticalOffset)
+                        val to = pixelOffset(dot, spacing, verticalOffset)
+                        val lerpedFrom =
+                            Offset(
+                                x = from.x + (to.x - from.x) * startLerp,
+                                y = from.y + (to.y - from.y) * startLerp,
+                            )
+                        drawLine(
+                            start = lerpedFrom,
+                            end = to,
+                            cap = StrokeCap.Round,
+                            alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+                            color = lineColor,
+                            strokeWidth = lineStrokeWidth,
                         )
-                    drawLine(
-                        start = lerpedFrom,
-                        end = to,
-                        cap = StrokeCap.Round,
-                        alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
-                        color = lineColor,
-                        strokeWidth = lineStrokeWidth,
-                    )
+                    }
+                }
+
+                // Draw the line between the most recently-selected dot and the input pointer
+                // position.
+                inputPosition?.let { lineEnd ->
+                    currentDot?.let { dot ->
+                        val from = pixelOffset(dot, spacing, verticalOffset)
+                        val lineLength =
+                            sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+                        drawLine(
+                            start = from,
+                            end = lineEnd,
+                            cap = StrokeCap.Round,
+                            alpha = lineAlpha(spacing, lineLength),
+                            color = lineColor,
+                            strokeWidth = lineStrokeWidth,
+                        )
+                    }
                 }
             }
 
-            // Draw the line between the most recently-selected dot and the input pointer position.
-            inputPosition?.let { lineEnd ->
-                currentDot?.let { dot ->
-                    val from = pixelOffset(dot, spacing, verticalOffset)
-                    val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
-                    drawLine(
-                        start = from,
-                        end = lineEnd,
-                        cap = StrokeCap.Round,
-                        alpha = lineAlpha(spacing, lineLength),
-                        color = lineColor,
-                        strokeWidth = lineStrokeWidth,
-                    )
-                }
+            // Draw each dot on the grid.
+            dots.forEach { dot ->
+                drawCircle(
+                    center = pixelOffset(dot, spacing, verticalOffset),
+                    color = dotColor,
+                    radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f),
+                )
             }
         }
-
-        // Draw each dot on the grid.
-        dots.forEach { dot ->
-            drawCircle(
-                center = pixelOffset(dot, spacing, verticalOffset),
-                color = dotColor,
-                radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f),
-            )
-        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 09706be..2c4dc80 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -32,7 +32,7 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import kotlinx.coroutines.flow.transform
 
 object Communal {
@@ -59,16 +59,19 @@
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
-    viewModel: CommunalViewModel,
+    viewModel: BaseCommunalViewModel,
 ) {
     val currentScene: SceneKey by
         viewModel.currentScene
             .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
             .collectAsState(TransitionSceneKey.Blank)
+    // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
+    // which can be opened from many locations.
+    val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
 
     // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
     var showSceneTransitionLayout by remember { mutableStateOf(true) }
-    if (!showSceneTransitionLayout) {
+    if (!showSceneTransitionLayout || !isKeyguardShowing) {
         return
     }
 
@@ -129,7 +132,7 @@
 /** Scene containing the glanceable hub UI. */
 @Composable
 private fun SceneScope.CommunalScene(
-    viewModel: CommunalViewModel,
+    viewModel: BaseCommunalViewModel,
     modifier: Modifier = Modifier,
 ) {
     Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
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 c80902e..e8ecd3a 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
@@ -19,6 +19,8 @@
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -31,10 +33,13 @@
 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.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
@@ -44,14 +49,14 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.res.R
@@ -59,39 +64,27 @@
 @Composable
 fun CommunalHub(
     modifier: Modifier = Modifier,
-    viewModel: CommunalViewModel,
+    viewModel: BaseCommunalViewModel,
+    onOpenWidgetPicker: (() -> Unit)? = null,
 ) {
     val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
-        LazyHorizontalGrid(
-            modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
-            rows = GridCells.Fixed(CommunalContentSize.FULL.span),
-            contentPadding = PaddingValues(horizontal = Dimensions.Spacing),
-            horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
-            verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
-        ) {
-            items(
-                count = communalContent.size,
-                key = { index -> communalContent[index].key },
-                span = { index -> GridItemSpan(communalContent[index].size.span) },
-            ) { index ->
-                CommunalContent(
-                    modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth),
-                    model = communalContent[index],
-                    viewModel = viewModel,
-                    deleteOnClick = viewModel::onDeleteWidget,
-                    size =
-                        SizeF(
-                            Dimensions.CardWidth.value,
-                            communalContent[index].size.dp().value,
-                        ),
-                )
+        CommunalHubLazyGrid(
+            modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
+            communalContent = communalContent,
+            isEditMode = viewModel.isEditMode,
+            viewModel = viewModel,
+        )
+        if (viewModel.isEditMode && onOpenWidgetPicker != null) {
+            IconButton(onClick = onOpenWidgetPicker) {
+                Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
             }
-        }
-        IconButton(onClick = viewModel::onOpenWidgetEditor) {
-            Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
+        } else {
+            IconButton(onClick = viewModel::onOpenWidgetEditor) {
+                Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor))
+            }
         }
 
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
@@ -106,16 +99,80 @@
     }
 }
 
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun CommunalHubLazyGrid(
+    communalContent: List<CommunalContentModel>,
+    isEditMode: Boolean,
+    viewModel: BaseCommunalViewModel,
+    modifier: Modifier = Modifier,
+) {
+    var gridModifier = modifier
+    val gridState = rememberLazyGridState()
+    var list = communalContent
+    var dragDropState: GridDragDropState? = null
+    if (isEditMode && viewModel is CommunalEditModeViewModel) {
+        val contentListState = rememberContentListState(communalContent, viewModel)
+        list = contentListState.list
+        dragDropState = rememberGridDragDropState(gridState, contentListState)
+        gridModifier = gridModifier.dragContainer(dragDropState)
+    }
+    LazyHorizontalGrid(
+        modifier = gridModifier,
+        state = gridState,
+        rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+        contentPadding = PaddingValues(horizontal = Dimensions.Spacing),
+        horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+        verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+    ) {
+        items(
+            count = list.size,
+            key = { index -> list[index].key },
+            span = { index -> GridItemSpan(list[index].size.span) },
+        ) { index ->
+            val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth)
+            val size =
+                SizeF(
+                    Dimensions.CardWidth.value,
+                    list[index].size.dp().value,
+                )
+            if (isEditMode && dragDropState != null) {
+                DraggableItem(dragDropState = dragDropState, enabled = true, index = index) {
+                    isDragging ->
+                    val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
+                    CommunalContent(
+                        modifier = cardModifier,
+                        deleteOnClick = viewModel::onDeleteWidget,
+                        elevation = elevation,
+                        model = list[index],
+                        viewModel = viewModel,
+                        size = size,
+                    )
+                }
+            } else {
+                CommunalContent(
+                    modifier = cardModifier,
+                    model = list[index],
+                    viewModel = viewModel,
+                    size = size,
+                )
+            }
+        }
+    }
+}
+
 @Composable
 private fun CommunalContent(
     model: CommunalContentModel,
-    viewModel: CommunalViewModel,
+    viewModel: BaseCommunalViewModel,
     size: SizeF,
-    deleteOnClick: (id: Int) -> Unit,
     modifier: Modifier = Modifier,
+    elevation: Dp = 0.dp,
+    deleteOnClick: ((id: Int) -> Unit)? = null,
 ) {
     when (model) {
-        is CommunalContentModel.Widget -> WidgetContent(model, size, deleteOnClick, modifier)
+        is CommunalContentModel.Widget ->
+            WidgetContent(model, size, elevation, deleteOnClick, modifier)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
         is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -126,18 +183,19 @@
 private fun WidgetContent(
     model: CommunalContentModel.Widget,
     size: SizeF,
-    deleteOnClick: (id: Int) -> Unit,
+    elevation: Dp,
+    deleteOnClick: ((id: Int) -> Unit)?,
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/309009246): update background color
-    Box(
+    Card(
         modifier = modifier.fillMaxSize().background(Color.White),
+        elevation = CardDefaults.cardElevation(draggedElevation = elevation),
     ) {
-        IconButton(onClick = { deleteOnClick(model.appWidgetId) }) {
-            Icon(
-                Icons.Default.Close,
-                LocalContext.current.getString(R.string.button_to_remove_widget)
-            )
+        if (deleteOnClick != null) {
+            IconButton(onClick = { deleteOnClick(model.appWidgetId) }) {
+                Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
+            }
         }
         AndroidView(
             modifier = modifier,
@@ -146,8 +204,6 @@
                     .createView(context, model.appWidgetId, model.providerInfo)
                     .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
             },
-            // For reusing composition in lazy lists.
-            onReset = {}
         )
     }
 }
@@ -173,7 +229,7 @@
 }
 
 @Composable
-private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) {
+private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
     AndroidView(
         modifier = modifier,
         factory = {
@@ -201,7 +257,7 @@
     }
 }
 
-private object Dimensions {
+object Dimensions {
     val CardWidth = 464.dp
     val CardHeightFull = 630.dp
     val CardHeightHalf = 307.dp
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
new file mode 100644
index 0000000..89c5765
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -0,0 +1,75 @@
+/*
+ * 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 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 com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+
+@Composable
+fun rememberContentListState(
+    communalContent: List<CommunalContentModel>,
+    viewModel: CommunalEditModeViewModel,
+): ContentListState {
+    return remember(communalContent) {
+        ContentListState(
+            communalContent,
+            viewModel::onDeleteWidget,
+            viewModel::onReorderWidgets,
+        )
+    }
+}
+
+/**
+ * Keeps the current state of the [CommunalContentModel] list being edited. [GridDragDropState]
+ * interacts with this class to update the order in the list. [onSaveList] should be called on
+ * dragging ends to persist the state in db for better performance.
+ */
+class ContentListState
+internal constructor(
+    communalContent: List<CommunalContentModel>,
+    private val onDeleteWidget: (id: Int) -> Unit,
+    private val onReorderWidgets: (ids: List<Int>) -> Unit,
+) {
+    var list by mutableStateOf(communalContent)
+        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)) }
+    }
+
+    /** 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) }
+            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)
+    }
+}
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
new file mode 100644
index 0000000..6cfa2f2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -0,0 +1,247 @@
+/*
+ * 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 androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridItemScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+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 kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+
+@Composable
+fun rememberGridDragDropState(
+    gridState: LazyGridState,
+    contentListState: ContentListState
+): GridDragDropState {
+    val scope = rememberCoroutineScope()
+    val state =
+        remember(gridState, contentListState) {
+            GridDragDropState(state = gridState, contentListState = contentListState, scope = scope)
+        }
+    LaunchedEffect(state) {
+        while (true) {
+            val diff = state.scrollChannel.receive()
+            gridState.scrollBy(diff)
+        }
+    }
+    return state
+}
+
+/**
+ * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are
+ * affected will dynamically get positioned and the state is tracked by [ContentListState]. When
+ * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to
+ * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the
+ * change.
+ */
+class GridDragDropState
+internal constructor(
+    private val state: LazyGridState,
+    private val contentListState: ContentListState,
+    private val scope: CoroutineScope,
+) {
+    var draggingItemIndex by mutableStateOf<Int?>(null)
+        private set
+
+    internal val scrollChannel = Channel<Float>()
+
+    private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
+    private var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
+    internal val draggingItemOffset: Offset
+        get() =
+            draggingItemLayoutInfo?.let { item ->
+                draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset()
+            }
+                ?: Offset.Zero
+
+    private val draggingItemLayoutInfo: LazyGridItemInfo?
+        get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
+
+    internal fun onDragStart(offset: Offset) {
+        state.layoutInfo.visibleItemsInfo
+            .firstOrNull { item ->
+                item.isEditable &&
+                    offset.x.toInt() in item.offset.x..item.offsetEnd.x &&
+                    offset.y.toInt() in item.offset.y..item.offsetEnd.y
+            }
+            ?.apply {
+                draggingItemIndex = index
+                draggingItemInitialOffset = this.offset.toOffset()
+            }
+    }
+
+    internal fun onDragInterrupted() {
+        if (draggingItemIndex != null) {
+            // persist list editing changes on dragging ends
+            contentListState.onSaveList()
+            draggingItemIndex = null
+        }
+        draggingItemDraggedDelta = Offset.Zero
+        draggingItemInitialOffset = Offset.Zero
+    }
+
+    internal fun onDrag(offset: Offset) {
+        draggingItemDraggedDelta += offset
+
+        val draggingItem = draggingItemLayoutInfo ?: return
+        val startOffset = draggingItem.offset.toOffset() + draggingItemOffset
+        val endOffset = startOffset + draggingItem.size.toSize()
+        val middleOffset = startOffset + (endOffset - startOffset) / 2f
+
+        val targetItem =
+            state.layoutInfo.visibleItemsInfo.find { item ->
+                item.isEditable &&
+                    middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
+                    middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
+                    draggingItem.index != item.index
+            }
+
+        if (targetItem != null) {
+            val scrollToIndex =
+                if (targetItem.index == state.firstVisibleItemIndex) {
+                    draggingItem.index
+                } else if (draggingItem.index == state.firstVisibleItemIndex) {
+                    targetItem.index
+                } else {
+                    null
+                }
+            if (scrollToIndex != null) {
+                scope.launch {
+                    // this is needed to neutralize automatic keeping the first item first.
+                    state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
+                    contentListState.onMove(draggingItem.index, targetItem.index)
+                }
+            } else {
+                contentListState.onMove(draggingItem.index, targetItem.index)
+            }
+            draggingItemIndex = targetItem.index
+        } else {
+            val overscroll = checkForOverscroll(startOffset, endOffset)
+            if (overscroll != 0f) {
+                scrollChannel.trySend(overscroll)
+            }
+            val removeOffset = checkForRemove(startOffset)
+            if (removeOffset != 0f) {
+                draggingItemIndex?.let {
+                    contentListState.onRemove(it)
+                    draggingItemIndex = null
+                }
+            }
+        }
+    }
+
+    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 {
+            draggingItemDraggedDelta.x > 0 ->
+                (endOffset.x - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
+            draggingItemDraggedDelta.x < 0 ->
+                (startOffset.x - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
+            else -> 0f
+        }
+    }
+
+    // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up
+    //  and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with
+    //  the Remove button.
+    private fun checkForRemove(startOffset: Offset): Float {
+        return if (draggingItemDraggedDelta.y < 0)
+            (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset)
+                .coerceAtMost(0f)
+        else 0f
+    }
+}
+
+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): Modifier {
+    return pointerInput(dragDropState) {
+        detectDragGesturesAfterLongPress(
+            onDrag = { change, offset ->
+                change.consume()
+                dragDropState.onDrag(offset = offset)
+            },
+            onDragStart = { offset -> dragDropState.onDragStart(offset) },
+            onDragEnd = { dragDropState.onDragInterrupted() },
+            onDragCancel = { dragDropState.onDragInterrupted() }
+        )
+    }
+}
+
+/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
+@ExperimentalFoundationApi
+@Composable
+fun LazyGridItemScope.DraggableItem(
+    dragDropState: GridDragDropState,
+    index: Int,
+    enabled: Boolean,
+    modifier: Modifier = Modifier,
+    content: @Composable (isDragging: Boolean) -> Unit
+) {
+    if (!enabled) {
+        return Box(modifier = modifier) { content(false) }
+    }
+    val dragging = index == dragDropState.draggingItemIndex
+    val draggingModifier =
+        if (dragging) {
+            Modifier.zIndex(1f).graphicsLayer {
+                translationX = dragDropState.draggingItemOffset.x
+                translationY = dragDropState.draggingItemOffset.y
+            }
+        } else {
+            Modifier.animateItemPlacement()
+        }
+    Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) {
+        content(dragging)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
index 1c993cf..e77ade9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
@@ -23,19 +23,9 @@
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.window.layout.FoldingFeature
 import androidx.window.layout.WindowInfoTracker
-
-sealed interface FoldPosture {
-    /** A foldable device that's fully closed/folded or a device that doesn't support folding. */
-    data object Folded : FoldPosture
-    /** A foldable that's halfway open with the hinge held vertically. */
-    data object Book : FoldPosture
-    /** A foldable that's halfway open with the hinge held horizontally. */
-    data object Tabletop : FoldPosture
-    /** A foldable that's fully unfolded / flat. */
-    data object FullyUnfolded : FoldPosture
-}
+import com.android.systemui.fold.ui.helper.FoldPosture
+import com.android.systemui.fold.ui.helper.foldPostureInternal
 
 /** Returns the [FoldPosture] of the device currently. */
 @Composable
@@ -48,32 +38,6 @@
         initialValue = FoldPosture.Folded,
         key1 = layoutInfo,
     ) {
-        value =
-            layoutInfo
-                ?.displayFeatures
-                ?.firstNotNullOfOrNull { it as? FoldingFeature }
-                .let { foldingFeature ->
-                    when (foldingFeature?.state) {
-                        null -> FoldPosture.Folded
-                        FoldingFeature.State.HALF_OPENED ->
-                            foldingFeature.orientation.toHalfwayPosture()
-                        FoldingFeature.State.FLAT ->
-                            if (foldingFeature.isSeparating) {
-                                // Dual screen device.
-                                foldingFeature.orientation.toHalfwayPosture()
-                            } else {
-                                FoldPosture.FullyUnfolded
-                            }
-                        else -> error("Unsupported state \"${foldingFeature.state}\"")
-                    }
-                }
-    }
-}
-
-private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture {
-    return when (this) {
-        FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop
-        FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book
-        else -> error("Unsupported orientation \"$this\"")
+        value = foldPostureInternal(layoutInfo)
     }
 }
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 ee310ab..3053654 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
@@ -33,6 +33,7 @@
 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
@@ -41,6 +42,7 @@
 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
@@ -88,7 +90,7 @@
     ) {
         LockscreenScene(
             viewProvider = viewProvider,
-            longPressViewModel = viewModel.longPress,
+            viewModel = viewModel,
             modifier = modifier,
         )
     }
@@ -108,9 +110,9 @@
 }
 
 @Composable
-private fun LockscreenScene(
+private fun SceneScope.LockscreenScene(
     viewProvider: () -> View,
-    longPressViewModel: KeyguardLongPressViewModel,
+    viewModel: LockscreenSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
     fun findSettingsMenu(): View {
@@ -121,7 +123,7 @@
         modifier = modifier,
     ) {
         LongPressSurface(
-            viewModel = longPressViewModel,
+            viewModel = viewModel.longPress,
             isSettingsMenuVisible = { findSettingsMenu().isVisible },
             settingsMenuBounds = {
                 val bounds = android.graphics.Rect()
@@ -141,6 +143,27 @@
             },
             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())
+            }
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index dd71dfa..c49c197 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -17,56 +17,197 @@
 
 package com.android.systemui.notifications.ui.composable
 
+import android.util.Log
 import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.ValueKey
+import com.android.compose.animation.scene.animateSharedFloatAsState
+import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.notifications.ui.composable.Notifications.SharedValues.SharedExpansionValue
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 
 object Notifications {
     object Elements {
-        val Notifications = ElementKey("Notifications")
+        val NotificationScrim = ElementKey("NotificationScrim")
+        val NotificationPlaceholder = ElementKey("NotificationPlaceholder")
+        val ShelfSpace = ElementKey("ShelfSpace")
+    }
+
+    object SharedValues {
+        val SharedExpansionValue = ValueKey("SharedExpansionValue")
+    }
+
+    enum class Form {
+        HunFromTop,
+        Stack,
+        HunFromBottom,
     }
 }
 
+/**
+ * Adds the space where heads up notifications can appear in the scene. This should generally be the
+ * entire size of the scene.
+ */
 @Composable
-fun SceneScope.Notifications(
+fun SceneScope.HeadsUpNotificationSpace(
+    viewModel: NotificationsPlaceholderViewModel,
+    modifier: Modifier = Modifier,
+    isPeekFromBottom: Boolean = false,
+) {
+    NotificationPlaceholder(
+        viewModel = viewModel,
+        form = if (isPeekFromBottom) Form.HunFromBottom else Form.HunFromTop,
+        modifier = modifier,
+    )
+}
+
+/** Adds the space where notification stack will appear in the scene. */
+@Composable
+fun SceneScope.NotificationStack(
+    viewModel: NotificationsPlaceholderViewModel,
+    isScrimVisible: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/272779828): implement.
-    Column(
-        modifier =
-            modifier
-                .element(key = Notifications.Elements.Notifications)
-                .fillMaxWidth()
-                .defaultMinSize(minHeight = 300.dp)
-                .clip(RoundedCornerShape(32.dp))
-                .background(MaterialTheme.colorScheme.surface)
-                .padding(16.dp),
-    ) {
-        Text(
-            text = "Notifications",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
-            style = MaterialTheme.typography.titleLarge,
-            color = MaterialTheme.colorScheme.onSurface,
-        )
-        Spacer(modifier = Modifier.weight(1f))
-        Text(
-            text = "Shelf",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
-            style = MaterialTheme.typography.titleSmall,
-            color = MaterialTheme.colorScheme.onSurface,
+    Box(modifier = modifier) {
+        if (isScrimVisible) {
+            Box(
+                modifier =
+                    Modifier.element(Notifications.Elements.NotificationScrim)
+                        .fillMaxSize()
+                        .clip(RoundedCornerShape(32.dp))
+                        .background(MaterialTheme.colorScheme.surface)
+            )
+        }
+        NotificationPlaceholder(
+            viewModel = viewModel,
+            form = Form.Stack,
+            modifier = Modifier.fillMaxSize(),
         )
     }
 }
+
+/**
+ * This may be added to the lockscreen to provide a space to the start of the lock icon where the
+ * short shelf has room to flow vertically below the lock icon, but to its start, allowing more
+ * notifications to fit in the stack itself. (see: b/213934746)
+ *
+ * NOTE: this is totally unused for now; it is here to clarify the future plan
+ */
+@Composable
+fun SceneScope.NotificationShelfSpace(
+    viewModel: NotificationsPlaceholderViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Text(
+        text = "Shelf Space",
+        modifier
+            .element(key = Notifications.Elements.ShelfSpace)
+            .fillMaxWidth()
+            .onSizeChanged { size: IntSize ->
+                debugLog(viewModel) { "SHELF onSizeChanged: size=$size" }
+            }
+            .onPlaced { coordinates: LayoutCoordinates ->
+                debugLog(viewModel) {
+                    ("SHELF onPlaced:" +
+                        " size=${coordinates.size}" +
+                        " position=${coordinates.positionInWindow()}" +
+                        " bounds=${coordinates.boundsInWindow()}")
+                }
+            }
+            .clip(RoundedCornerShape(24.dp))
+            .background(MaterialTheme.colorScheme.primaryContainer)
+            .padding(16.dp),
+        style = MaterialTheme.typography.titleLarge,
+        color = MaterialTheme.colorScheme.onPrimaryContainer,
+    )
+}
+
+@Composable
+private fun SceneScope.NotificationPlaceholder(
+    viewModel: NotificationsPlaceholderViewModel,
+    form: Form,
+    modifier: Modifier = Modifier,
+) {
+    val elementKey = Notifications.Elements.NotificationPlaceholder
+    Box(
+        modifier =
+            modifier
+                .element(elementKey)
+                .debugBackground(viewModel)
+                .onSizeChanged { size: IntSize ->
+                    debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
+                }
+                .onPlaced { coordinates: LayoutCoordinates ->
+                    debugLog(viewModel) {
+                        "STACK onPlaced:" +
+                            " size=${coordinates.size}" +
+                            " position=${coordinates.positionInWindow()}" +
+                            " bounds=${coordinates.boundsInWindow()}"
+                    }
+                    val boundsInWindow = coordinates.boundsInWindow()
+                    viewModel.onBoundsChanged(
+                        top = boundsInWindow.top,
+                        bottom = boundsInWindow.bottom,
+                    )
+                }
+    ) {
+        val animatedExpansion by
+            animateSharedFloatAsState(
+                value = if (form == Form.HunFromTop) 0f else 1f,
+                key = SharedExpansionValue,
+                element = elementKey
+            )
+        debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" }
+        if (viewModel.isPlaceholderTextVisible) {
+            Text(
+                text = "Notifications",
+                style = MaterialTheme.typography.titleLarge,
+                color = MaterialTheme.colorScheme.onSurface,
+                modifier = Modifier.align(Alignment.Center),
+            )
+        }
+    }
+}
+
+private inline fun debugLog(
+    viewModel: NotificationsPlaceholderViewModel,
+    msg: () -> Any,
+) {
+    if (viewModel.isDebugLoggingEnabled) {
+        Log.d(TAG, msg().toString())
+    }
+}
+
+private fun Modifier.debugBackground(
+    viewModel: NotificationsPlaceholderViewModel,
+    color: Color = DEBUG_COLOR,
+): Modifier =
+    if (viewModel.isVisualDebuggingEnabled) {
+        background(color)
+    } else {
+        this
+    }
+
+private const val TAG = "FlexiNotifs"
+private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 28a4801..7654683 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -25,9 +25,6 @@
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
@@ -36,7 +33,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
@@ -76,9 +72,6 @@
                 .element(QuickSettings.Elements.Content)
                 .fillMaxWidth()
                 .defaultMinSize(minHeight = 300.dp)
-                .clip(RoundedCornerShape(32.dp))
-                .background(MaterialTheme.colorScheme.primary)
-                .padding(1.dp),
     ) {
         QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state)
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index b9451d1..871d9f9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -23,13 +23,15 @@
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.shrinkVertically
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
@@ -43,12 +45,14 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -101,53 +105,65 @@
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280887232): implement the real UI.
-    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
-    val collapsedHeaderHeight =
-        with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier =
-            modifier
-                .fillMaxSize()
-                .clickable(onClick = { viewModel.onContentClicked() })
-                .padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
-    ) {
-        when (LocalWindowSizeClass.current.widthSizeClass) {
-            WindowWidthSizeClass.Compact ->
-                AnimatedVisibility(
-                    visible = !isCustomizing,
-                    enter =
-                        expandVertically(
-                            animationSpec = tween(1000),
-                            initialHeight = { collapsedHeaderHeight },
-                        ) + fadeIn(tween(1000)),
-                    exit =
-                        shrinkVertically(
-                            animationSpec = tween(1000),
-                            targetHeight = { collapsedHeaderHeight },
-                            shrinkTowards = Alignment.Top,
-                        ) + fadeOut(tween(1000)),
-                ) {
-                    ExpandedShadeHeader(
-                        viewModel = viewModel.shadeHeaderViewModel,
-                        createTintedIconManager = createTintedIconManager,
-                        createBatteryMeterViewController = createBatteryMeterViewController,
-                        statusBarIconController = statusBarIconController,
-                    )
+    Box(modifier = modifier.fillMaxSize()) {
+        Box(modifier = Modifier.fillMaxSize()) {
+            val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+            val collapsedHeaderHeight =
+                with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
+            Spacer(
+                modifier =
+                    Modifier.element(Shade.Elements.ScrimBackground)
+                        .fillMaxSize()
+                        .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+            )
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier =
+                    Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
+            ) {
+                when (LocalWindowSizeClass.current.widthSizeClass) {
+                    WindowWidthSizeClass.Compact ->
+                        AnimatedVisibility(
+                            visible = !isCustomizing,
+                            enter =
+                                expandVertically(
+                                    animationSpec = tween(1000),
+                                    initialHeight = { collapsedHeaderHeight },
+                                ) + fadeIn(tween(1000)),
+                            exit =
+                                shrinkVertically(
+                                    animationSpec = tween(1000),
+                                    targetHeight = { collapsedHeaderHeight },
+                                    shrinkTowards = Alignment.Top,
+                                ) + fadeOut(tween(1000)),
+                        ) {
+                            ExpandedShadeHeader(
+                                viewModel = viewModel.shadeHeaderViewModel,
+                                createTintedIconManager = createTintedIconManager,
+                                createBatteryMeterViewController = createBatteryMeterViewController,
+                                statusBarIconController = statusBarIconController,
+                            )
+                        }
+                    else ->
+                        CollapsedShadeHeader(
+                            viewModel = viewModel.shadeHeaderViewModel,
+                            createTintedIconManager = createTintedIconManager,
+                            createBatteryMeterViewController = createBatteryMeterViewController,
+                            statusBarIconController = statusBarIconController,
+                        )
                 }
-            else ->
-                CollapsedShadeHeader(
-                    viewModel = viewModel.shadeHeaderViewModel,
-                    createTintedIconManager = createTintedIconManager,
-                    createBatteryMeterViewController = createBatteryMeterViewController,
-                    statusBarIconController = statusBarIconController,
+                Spacer(modifier = Modifier.height(16.dp))
+                QuickSettings(
+                    modifier = Modifier.fillMaxHeight(),
+                    viewModel.qsSceneAdapter,
+                    QSSceneAdapter.State.QS
                 )
+            }
         }
-        Spacer(modifier = Modifier.height(16.dp))
-        QuickSettings(
-            modifier = Modifier.fillMaxHeight(),
-            viewModel.qsSceneAdapter,
-            QSSceneAdapter.State.QS
+        HeadsUpNotificationSpace(
+            viewModel = viewModel.notifications,
+            isPeekFromBottom = true,
+            modifier = Modifier.padding(16.dp).fillMaxSize(),
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index f35ea83..bded98d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,15 +17,20 @@
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -36,7 +41,11 @@
  * content from the scene framework.
  */
 @SysUISingleton
-class GoneScene @Inject constructor() : ComposableScene {
+class GoneScene
+@Inject
+constructor(
+    private val notificationsViewModel: NotificationsPlaceholderViewModel,
+) : ComposableScene {
     override val key = SceneKey.Gone
 
     override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
@@ -56,6 +65,11 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        Box(modifier = modifier)
+        Box(modifier = modifier) {
+            HeadsUpNotificationSpace(
+                viewModel = notificationsViewModel,
+                modifier = Modifier.padding(16.dp).fillMaxSize(),
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 45df2b1..6bb525a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,10 +3,12 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.scene.ui.composable.Shade
 
 fun TransitionBuilder.goneToShadeTransition() {
     spec = tween(durationMillis = 500)
 
     translate(Shade.rootElementKey, Edge.Top, true)
+    fade(Notifications.Elements.NotificationScrim)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index fadbdce..ebc343d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -10,7 +10,6 @@
 fun TransitionBuilder.lockscreenToShadeTransition() {
     spec = tween(durationMillis = 500)
 
-    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
     translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
     fractionRange(end = 0.5f) {
         fade(Shade.Elements.ScrimBackground)
@@ -20,5 +19,5 @@
             startsOutsideLayoutBounds = false,
         )
     }
-    fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+    fractionRange(start = 0.5f) { fade(Notifications.Elements.NotificationScrim) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index 5616175..d5c2a03 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -10,7 +10,7 @@
 fun TransitionBuilder.shadeToQuickSettingsTransition() {
     spec = tween(durationMillis = 500)
 
-    translate(Notifications.Elements.Notifications, Edge.Bottom)
+    translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
     timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
 
     translate(ShadeHeader.Elements.CollapsedContent, y = ShadeHeader.Dimensions.CollapsedHeight)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index a02f046..2df151b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.notifications.ui.composable.NotificationStack
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
@@ -160,7 +160,11 @@
                 QSSceneAdapter.State.QQS
             )
             Spacer(modifier = Modifier.height(16.dp))
-            Notifications(modifier = Modifier.weight(1f))
+            NotificationStack(
+                viewModel = viewModel.notifications,
+                isScrimVisible = true,
+                modifier = Modifier.weight(1f),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 3b999e30..2b11952 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
@@ -25,16 +26,17 @@
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.geometry.isUnspecified
 import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.layout.IntermediateMeasureScope
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.Constraints
@@ -46,6 +48,7 @@
 import kotlinx.coroutines.launch
 
 /** An element on screen, that can be composed in one or more scenes. */
+@Stable
 internal class Element(val key: ElementKey) {
     /**
      * The last values of this element, coming from any scene. Note that this value will be unstable
@@ -90,6 +93,7 @@
     }
 
     /** The target values of this element in a given scene. */
+    @Stable
     class TargetValues(val scene: SceneKey) {
         val lastValues = Values()
 
@@ -107,6 +111,7 @@
     }
 
     /** A shared value of this element. */
+    @Stable
     class SharedValue<T>(val key: ValueKey, initialValue: T) {
         var value by mutableStateOf(initialValue)
     }
@@ -126,6 +131,7 @@
 
 /** The implementation of [SceneScope.element]. */
 @OptIn(ExperimentalComposeUiApi::class)
+@Stable
 internal fun Modifier.element(
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
@@ -144,24 +150,9 @@
                 ?: Element.TargetValues(scene.key).also { element.sceneValues[scene.key] = it }
     }
 
-    return this.then(ElementModifier(layoutImpl, element, sceneValues))
-        .drawWithContent {
-            if (shouldDrawElement(layoutImpl, scene, element)) {
-                val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
-                if (drawScale == Scale.Default) {
-                    drawContent()
-                } else {
-                    scale(
-                        drawScale.scaleX,
-                        drawScale.scaleY,
-                        if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
-                    ) {
-                        this@drawWithContent.drawContent()
-                    }
-                }
-            }
-        }
-        .modifierTransformations(layoutImpl, scene, element, sceneValues)
+    return this.then(ElementModifier(layoutImpl, scene, element, sceneValues))
+        // TODO(b/311132415): Move this into ElementNode once we can create a delegate
+        // IntermediateLayoutModifierNode.
         .intermediateLayout { measurable, constraints ->
             val placeable =
                 measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
@@ -178,22 +169,25 @@
  */
 private data class ElementModifier(
     private val layoutImpl: SceneTransitionLayoutImpl,
+    private val scene: Scene,
     private val element: Element,
     private val sceneValues: Element.TargetValues,
 ) : ModifierNodeElement<ElementNode>() {
-    override fun create(): ElementNode = ElementNode(layoutImpl, element, sceneValues)
+    override fun create(): ElementNode = ElementNode(layoutImpl, scene, element, sceneValues)
 
     override fun update(node: ElementNode) {
-        node.update(layoutImpl, element, sceneValues)
+        node.update(layoutImpl, scene, element, sceneValues)
     }
 }
 
 internal class ElementNode(
     layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
     element: Element,
     sceneValues: Element.TargetValues,
-) : Modifier.Node() {
+) : Modifier.Node(), DrawModifierNode {
     private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl
+    private var scene: Scene = scene
     private var element: Element = element
     private var sceneValues: Element.TargetValues = sceneValues
 
@@ -239,15 +233,34 @@
 
     fun update(
         layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
         element: Element,
         sceneValues: Element.TargetValues,
     ) {
         removeNodeFromSceneValues()
         this.layoutImpl = layoutImpl
+        this.scene = scene
         this.element = element
         this.sceneValues = sceneValues
         addNodeToSceneValues()
     }
+
+    override fun ContentDrawScope.draw() {
+        if (shouldDrawElement(layoutImpl, scene, element)) {
+            val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
+            if (drawScale == Scale.Default) {
+                drawContent()
+            } else {
+                scale(
+                    drawScale.scaleX,
+                    drawScale.scaleY,
+                    if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
+                ) {
+                    this@draw.drawContent()
+                }
+            }
+        }
+    }
 }
 
 private fun shouldDrawElement(
@@ -332,39 +345,6 @@
 }
 
 /**
- * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied
- * throughout the current transition, if any.
- */
-private fun Modifier.modifierTransformations(
-    layoutImpl: SceneTransitionLayoutImpl,
-    scene: Scene,
-    element: Element,
-    sceneValues: Element.TargetValues,
-): Modifier {
-    when (val state = layoutImpl.state.transitionState) {
-        is TransitionState.Idle -> return this
-        is TransitionState.Transition -> {
-            val fromScene = state.fromScene
-            val toScene = state.toScene
-            if (fromScene == toScene) {
-                // Same as idle.
-                return this
-            }
-
-            return layoutImpl.transitions
-                .transitionSpec(fromScene, state.toScene)
-                .transformations(element.key, scene.key)
-                .modifier
-                .fold(this) { modifier, transformation ->
-                    with(transformation) {
-                        modifier.transform(layoutImpl, scene, element, sceneValues)
-                    }
-                }
-        }
-    }
-}
-
-/**
  * Whether the element is opaque or not.
  *
  * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 5b752eb..84d3b86 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -17,11 +17,13 @@
 package com.android.compose.animation.scene
 
 import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Stable
 
 /**
  * A base class to create unique keys, associated to an [identity] that is used to check the
  * equality of two key instances.
  */
+@Stable
 sealed class Key(val debugName: String, val identity: Any) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index d48781a..a0fba80 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -23,20 +23,25 @@
 import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.gestures.verticalDrag
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
@@ -56,7 +61,7 @@
  * dragged) and a second pointer is down and dragged. This is an implementation detail that might
  * change in the future.
  */
-// TODO(b/291055080): Migrate to the Modifier.Node API.
+@Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
     enabled: Boolean,
@@ -64,22 +69,88 @@
     onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
     onDragDelta: (Float) -> Unit,
     onDragStopped: (velocity: Float) -> Unit,
-): Modifier = composed {
-    val onDragStarted by rememberUpdatedState(onDragStarted)
-    val onDragStopped by rememberUpdatedState(onDragStopped)
-    val onDragDelta by rememberUpdatedState(onDragDelta)
-    val startDragImmediately by rememberUpdatedState(startDragImmediately)
+): Modifier =
+    this.then(
+        MultiPointerDraggableElement(
+            orientation,
+            enabled,
+            startDragImmediately,
+            onDragStarted,
+            onDragDelta,
+            onDragStopped,
+        )
+    )
 
-    val velocityTracker = remember { VelocityTracker() }
-    val maxFlingVelocity =
-        LocalViewConfiguration.current.maximumFlingVelocity.let { max ->
-            val maxF = max.toFloat()
-            Velocity(maxF, maxF)
+private data class MultiPointerDraggableElement(
+    private val orientation: Orientation,
+    private val enabled: Boolean,
+    private val startDragImmediately: Boolean,
+    private val onDragStarted:
+        (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+    private val onDragDelta: (Float) -> Unit,
+    private val onDragStopped: (velocity: Float) -> Unit,
+) : ModifierNodeElement<MultiPointerDraggableNode>() {
+    override fun create(): MultiPointerDraggableNode =
+        MultiPointerDraggableNode(
+            orientation = orientation,
+            enabled = enabled,
+            startDragImmediately = startDragImmediately,
+            onDragStarted = onDragStarted,
+            onDragDelta = onDragDelta,
+            onDragStopped = onDragStopped,
+        )
+
+    override fun update(node: MultiPointerDraggableNode) {
+        node.orientation = orientation
+        node.enabled = enabled
+        node.startDragImmediately = startDragImmediately
+        node.onDragStarted = onDragStarted
+        node.onDragDelta = onDragDelta
+        node.onDragStopped = onDragStopped
+    }
+}
+
+private class MultiPointerDraggableNode(
+    orientation: Orientation,
+    enabled: Boolean,
+    var startDragImmediately: Boolean,
+    var onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+    var onDragDelta: (Float) -> Unit,
+    var onDragStopped: (velocity: Float) -> Unit,
+) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode {
+    private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
+    private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
+    private val velocityTracker = VelocityTracker()
+
+    var enabled: Boolean = enabled
+        set(value) {
+            // Reset the pointer input whenever enabled changed.
+            if (value != field) {
+                field = value
+                delegate.resetPointerInputHandler()
+            }
         }
 
-    pointerInput(enabled, orientation, maxFlingVelocity) {
+    var orientation: Orientation = orientation
+        set(value) {
+            // Reset the pointer input whenever enabled orientation.
+            if (value != field) {
+                field = value
+                delegate.resetPointerInputHandler()
+            }
+        }
+
+    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize
+    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+
+    private suspend fun PointerInputScope.pointerInput() {
         if (!enabled) {
-            return@pointerInput
+            return
         }
 
         val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
@@ -90,6 +161,12 @@
         val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
 
         val onDragEnd: () -> Unit = {
+            val maxFlingVelocity =
+                currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
+                    val maxF = max.toFloat()
+                    Velocity(maxF, maxF)
+                }
+
             val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
             onDragStopped(
                 when (orientation) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
new file mode 100644
index 0000000..560e92b
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.withSaveLayer
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.toSize
+
+internal fun Modifier.punchHole(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: ElementKey,
+    bounds: ElementKey,
+    shape: Shape,
+): Modifier = this.then(PunchHoleElement(layoutImpl, element, bounds, shape))
+
+private data class PunchHoleElement(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: ElementKey,
+    private val bounds: ElementKey,
+    private val shape: Shape,
+) : ModifierNodeElement<PunchHoleNode>() {
+    override fun create(): PunchHoleNode = PunchHoleNode(layoutImpl, element, bounds, shape)
+
+    override fun update(node: PunchHoleNode) {
+        node.layoutImpl = layoutImpl
+        node.element = element
+        node.bounds = bounds
+        node.shape = shape
+    }
+}
+
+private class PunchHoleNode(
+    var layoutImpl: SceneTransitionLayoutImpl,
+    var element: ElementKey,
+    var bounds: ElementKey,
+    var shape: Shape,
+) : Modifier.Node(), DrawModifierNode {
+    private var lastSize: Size = Size.Unspecified
+    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
+    private var lastOutline: Outline? = null
+
+    override fun ContentDrawScope.draw() {
+        val bounds = layoutImpl.elements[bounds]
+
+        if (
+            bounds == null ||
+                bounds.lastSharedValues.size == Element.SizeUnspecified ||
+                bounds.lastSharedValues.offset == Offset.Unspecified
+        ) {
+            drawContent()
+            return
+        }
+
+        val element = layoutImpl.elements.getValue(element)
+        drawIntoCanvas { canvas ->
+            canvas.withSaveLayer(size.toRect(), Paint()) {
+                drawContent()
+
+                val offset = bounds.lastSharedValues.offset - element.lastSharedValues.offset
+                translate(offset.x, offset.y) { drawHole(bounds) }
+            }
+        }
+    }
+
+    private fun DrawScope.drawHole(bounds: Element) {
+        val boundsSize = bounds.lastSharedValues.size.toSize()
+        if (shape == RectangleShape) {
+            drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut)
+            return
+        }
+
+        val outline =
+            if (boundsSize == lastSize && layoutDirection == lastLayoutDirection) {
+                lastOutline!!
+            } else {
+                val newOutline = shape.createOutline(boundsSize, layoutDirection, this)
+                lastSize = boundsSize
+                lastLayoutDirection = layoutDirection
+                lastOutline = newOutline
+                newOutline
+            }
+
+        drawOutline(
+            outline,
+            Color.Black,
+            blendMode = BlendMode.DstOut,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 857a596..f5561cb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -19,20 +19,24 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.layout.intermediateLayout
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.zIndex
 
 /** A scene in a [SceneTransitionLayout]. */
+@Stable
 internal class Scene(
     val key: SceneKey,
     layoutImpl: SceneTransitionLayoutImpl,
@@ -104,11 +108,13 @@
     ): State<T> {
         val element =
             element?.let { key ->
-                layoutImpl.elements[key]
-                    ?: error(
-                        "Element $key is not composed. Make sure to call animateSharedXAsState " +
-                            "*after* Modifier.element(key)."
-                    )
+                Snapshot.withoutReadObservation {
+                    layoutImpl.elements[key]
+                        ?: error(
+                            "Element $key is not composed. Make sure to call " +
+                                "animateSharedXAsState *after* Modifier.element(key)."
+                        )
+                }
             }
 
         return animateSharedValueAsState(
@@ -130,4 +136,10 @@
     ) {
         MovableElement(layoutImpl, scene, key, modifier, content)
     }
+
+    override fun Modifier.punchHole(
+        element: ElementKey,
+        bounds: ElementKey,
+        shape: Shape
+    ): Modifier = punchHole(layoutImpl, element, bounds, shape)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index c51287a..b00c886 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -536,24 +535,6 @@
 ) : NestedScrollHandler {
     override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
-    private fun Offset.toAmount() =
-        when (gestureHandler.orientation) {
-            Orientation.Horizontal -> x
-            Orientation.Vertical -> y
-        }
-
-    private fun Velocity.toAmount() =
-        when (gestureHandler.orientation) {
-            Orientation.Horizontal -> x
-            Orientation.Vertical -> y
-        }
-
-    private fun Float.toOffset() =
-        when (gestureHandler.orientation) {
-            Orientation.Horizontal -> Offset(x = this, y = 0f)
-            Orientation.Vertical -> Offset(x = 0f, y = this)
-        }
-
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
         // If we performed a long gesture before entering priority mode, we would have to avoid
         // moving on to the next scene.
@@ -591,13 +572,12 @@
         }
 
         return PriorityNestedScrollConnection(
+            orientation = gestureHandler.orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
-                canChangeScene = offsetBeforeStart == Offset.Zero
+                canChangeScene = offsetBeforeStart == 0f
 
                 val canInterceptSwipeTransition =
-                    canChangeScene &&
-                        gestureHandler.isDrivingTransition &&
-                        offsetAvailable.toAmount() != 0f
+                    canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
                 val progress = gestureHandler.swipeTransition.progress
@@ -618,15 +598,14 @@
                 !shouldSnapToIdle
             },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
-                val amount = offsetAvailable.toAmount()
                 val behavior: NestedScrollBehavior =
                     when {
-                        amount > 0 -> startBehavior
-                        amount < 0 -> endBehavior
+                        offsetAvailable > 0f -> startBehavior
+                        offsetAvailable < 0f -> endBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
-                val isZeroOffset = offsetBeforeStart == Offset.Zero
+                val isZeroOffset = offsetBeforeStart == 0f
 
                 when (behavior) {
                     NestedScrollBehavior.DuringTransitionBetweenScenes -> {
@@ -635,30 +614,29 @@
                     }
                     NestedScrollBehavior.EdgeNoOverscroll -> {
                         canChangeScene = isZeroOffset
-                        isZeroOffset && hasNextScene(amount)
+                        isZeroOffset && hasNextScene(offsetAvailable)
                     }
                     NestedScrollBehavior.EdgeWithOverscroll -> {
                         canChangeScene = isZeroOffset
-                        hasNextScene(amount)
+                        hasNextScene(offsetAvailable)
                     }
                     NestedScrollBehavior.Always -> {
                         canChangeScene = true
-                        hasNextScene(amount)
+                        hasNextScene(offsetAvailable)
                     }
                 }
             },
             canStartPostFling = { velocityAvailable ->
-                val amount = velocityAvailable.toAmount()
                 val behavior: NestedScrollBehavior =
                     when {
-                        amount > 0 -> startBehavior
-                        amount < 0 -> endBehavior
+                        velocityAvailable > 0f -> startBehavior
+                        velocityAvailable < 0f -> endBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
                 // We could start an overscroll animation
                 canChangeScene = false
-                behavior.canStartOnPostFling && hasNextScene(amount)
+                behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
             },
             canContinueScroll = { true },
             onStart = {
@@ -671,24 +649,22 @@
             },
             onScroll = { offsetAvailable ->
                 if (gestureHandler.gestureWithPriority != this) {
-                    return@PriorityNestedScrollConnection Offset.Zero
+                    return@PriorityNestedScrollConnection 0f
                 }
 
-                val amount = offsetAvailable.toAmount()
-
                 // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
                 // initiated in a nested child.
-                gestureHandler.onDrag(amount)
+                gestureHandler.onDrag(offsetAvailable)
 
-                amount.toOffset()
+                offsetAvailable
             },
             onStop = { velocityAvailable ->
                 if (gestureHandler.gestureWithPriority != this) {
-                    return@PriorityNestedScrollConnection Velocity.Zero
+                    return@PriorityNestedScrollConnection 0f
                 }
 
                 gestureHandler.onDragStopped(
-                    velocity = velocityAvailable.toAmount(),
+                    velocity = velocityAvailable,
                     canChangeScene = canChangeScene
                 )
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 30d13df..07add77 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -19,10 +19,12 @@
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
 
@@ -94,6 +96,7 @@
 @DslMarker annotation class ElementDsl
 
 @ElementDsl
+@Stable
 interface SceneScope {
     /** The state of the [SceneTransitionLayout] in which this scene is contained. */
     val layoutState: SceneTransitionLayoutState
@@ -177,6 +180,18 @@
         lerp: (start: T, stop: T, fraction: Float) -> T,
         canOverflow: Boolean,
     ): State<T>
+
+    /**
+     * Punch a hole in this [element] using the bounds of [bounds] in [scene] and the given [shape].
+     *
+     * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
+     * This can be used to make content drawn below an opaque element visible. For example, if we
+     * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
+     * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big
+     * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be
+     * the result.
+     */
+    fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier
 }
 
 // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 60f385a..02ddccb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
@@ -41,6 +42,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 
+@Stable
 internal class SceneTransitionLayoutImpl(
     onChangeScene: (SceneKey) -> Unit,
     builder: SceneTransitionLayoutScope.() -> Unit,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 64c9775..f48e914 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -16,11 +16,13 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 
 /** The state of a [SceneTransitionLayout]. */
+@Stable
 class SceneTransitionLayoutState(initialScene: SceneKey) {
     /**
      * The current [TransitionState]. All values read here are backed by the Snapshot system.
@@ -29,7 +31,6 @@
      * [SceneTransitionLayoutState.observableTransitionState] instead.
      */
     var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
-        internal set
 
     /**
      * Whether we are transitioning, optionally restricting the check to the transition between
@@ -46,8 +47,15 @@
         return (from == null || transition.fromScene == from) &&
             (to == null || transition.toScene == to)
     }
+
+    /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
+    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+        return isTransitioning(from = scene, to = other) ||
+            isTransitioning(from = other, to = scene)
+    }
 }
 
+@Stable
 sealed interface TransitionState {
     /**
      * The current effective scene. If a new transition was triggered, it would start from this
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2172ed3..f91895b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
@@ -27,7 +28,6 @@
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
-import com.android.compose.animation.scene.transformation.ModifierTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
@@ -93,6 +93,7 @@
 }
 
 /** The definition of a transition between [from] and [to]. */
+@Stable
 data class TransitionSpec(
     val from: SceneKey?,
     val to: SceneKey?,
@@ -122,7 +123,6 @@
         scene: SceneKey,
     ): ElementTransformations {
         var shared: SharedElementTransformation? = null
-        val modifier = mutableListOf<ModifierTransformation>()
         var offset: PropertyTransformation<Offset>? = null
         var size: PropertyTransformation<IntSize>? = null
         var drawScale: PropertyTransformation<Scale>? = null
@@ -166,12 +166,11 @@
                     throwIfNotNull(shared, element, name = "shared")
                     shared = transformation
                 }
-                is ModifierTransformation -> modifier.add(transformation)
                 is PropertyTransformation<*> -> onPropertyTransformation(transformation)
             }
         }
 
-        return ElementTransformations(shared, modifier, offset, size, drawScale, alpha)
+        return ElementTransformations(shared, offset, size, drawScale, alpha)
     }
 
     private fun throwIfNotNull(
@@ -188,7 +187,6 @@
 /** The transformations of an element during a transition. */
 internal class ElementTransformations(
     val shared: SharedElementTransformation?,
-    val modifier: List<ModifierTransformation>,
     val offset: PropertyTransformation<Offset>?,
     val size: PropertyTransformation<IntSize>?,
     val drawScale: PropertyTransformation<Scale>?,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index ca66dff5..f820074 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -18,8 +18,6 @@
 
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
@@ -131,19 +129,6 @@
     )
 
     /**
-     * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
-     * using the given [shape].
-     *
-     * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
-     * This can be used to make content drawn below an opaque element visible. For example, if we
-     * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
-     * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big
-     * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be
-     * the result.
-     */
-    fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape)
-
-    /**
      * Adds the transformations in [builder] but in reversed order. This allows you to partially
      * reuse the definition of the transition from scene `Foo` to scene `Bar` inside the definition
      * of the transition from scene `Bar` to scene `Foo`.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index d490989..8c0a5a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
@@ -30,7 +29,6 @@
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
 import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.PunchHole
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
@@ -93,10 +91,6 @@
         spec.vectorize(Float.VectorConverter).durationMillis
     }
 
-    override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) {
-        transformations.add(PunchHole(matcher, bounds, shape))
-    }
-
     override fun reversed(builder: TransitionBuilder.() -> Unit) {
         reversed = true
         builder()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
deleted file mode 100644
index 984086b..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.animation.scene.transformation
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.geometry.toRect
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Paint
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.graphics.withSaveLayer
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-import com.android.compose.animation.scene.Element
-import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.Scene
-import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-
-/** Punch a hole in an element using the bounds of another element and a given [shape]. */
-internal class PunchHole(
-    override val matcher: ElementMatcher,
-    private val bounds: ElementKey,
-    private val shape: Shape,
-) : ModifierTransformation {
-
-    private var lastSize: Size = Size.Unspecified
-    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
-    private var lastOutline: Outline? = null
-
-    override fun Modifier.transform(
-        layoutImpl: SceneTransitionLayoutImpl,
-        scene: Scene,
-        element: Element,
-        sceneValues: Element.TargetValues,
-    ): Modifier {
-        return drawWithContent {
-            val bounds = layoutImpl.elements[bounds]
-            if (
-                bounds == null ||
-                    bounds.lastSharedValues.size == Element.SizeUnspecified ||
-                    bounds.lastSharedValues.offset == Offset.Unspecified
-            ) {
-                drawContent()
-                return@drawWithContent
-            }
-            drawIntoCanvas { canvas ->
-                canvas.withSaveLayer(size.toRect(), Paint()) {
-                    drawContent()
-
-                    val offset = bounds.lastSharedValues.offset - element.lastSharedValues.offset
-                    translate(offset.x, offset.y) { drawHole(bounds) }
-                }
-            }
-        }
-    }
-
-    private fun DrawScope.drawHole(bounds: Element) {
-        val boundsSize = bounds.lastSharedValues.size.toSize()
-        if (shape == RectangleShape) {
-            drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut)
-            return
-        }
-
-        val outline =
-            if (boundsSize == lastSize && layoutDirection == lastLayoutDirection) {
-                lastOutline!!
-            } else {
-                val newOutline = shape.createOutline(boundsSize, layoutDirection, this)
-                lastSize = boundsSize
-                lastLayoutDirection = layoutDirection
-                lastOutline = newOutline
-                newOutline
-            }
-
-        drawOutline(
-            outline,
-            Color.Black,
-            blendMode = BlendMode.DstOut,
-        )
-    }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 0db8469..2069355 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -16,7 +16,6 @@
 
 package com.android.compose.animation.scene.transformation
 
-import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
@@ -52,19 +51,6 @@
     internal val scenePicker: SharedElementScenePicker,
 ) : Transformation
 
-/** A transformation that is applied on the element during the whole transition. */
-internal interface ModifierTransformation : Transformation {
-    /** Apply the transformation to [element]. */
-    // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
-    // to these internal classes.
-    fun Modifier.transform(
-        layoutImpl: SceneTransitionLayoutImpl,
-        scene: Scene,
-        element: Element,
-        sceneValues: Element.TargetValues,
-    ): Modifier
-}
-
 /** A transformation that changes the value of an element property, like its size or offset. */
 internal sealed interface PropertyTransformation<T> : Transformation {
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 824c10b..a5fd1bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,10 +16,12 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.Velocity
+import com.android.compose.ui.util.SpaceVectorConverter
 
 /**
  * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
@@ -147,3 +149,35 @@
         return onStop(velocity)
     }
 }
+
+fun PriorityNestedScrollConnection(
+    orientation: Orientation,
+    canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+    canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+    canStartPostFling: (velocityAvailable: Float) -> Boolean,
+    canContinueScroll: () -> Boolean,
+    onStart: () -> Unit,
+    onScroll: (offsetAvailable: Float) -> Float,
+    onStop: (velocityAvailable: Float) -> Float,
+) =
+    with(SpaceVectorConverter(orientation)) {
+        PriorityNestedScrollConnection(
+            canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
+                canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
+            },
+            canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
+                canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
+            },
+            canStartPostFling = { velocityAvailable: Velocity ->
+                canStartPostFling(velocityAvailable.toFloat())
+            },
+            canContinueScroll = canContinueScroll,
+            onStart = onStart,
+            onScroll = { offsetAvailable: Offset ->
+                onScroll(offsetAvailable.toFloat()).toOffset()
+            },
+            onStop = { velocityAvailable: Velocity ->
+                onStop(velocityAvailable.toFloat()).toVelocity()
+            },
+        )
+    }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
new file mode 100644
index 0000000..a13e944
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.compose.ui.util
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Velocity
+
+interface SpaceVectorConverter {
+    fun Offset.toFloat(): Float
+    fun Velocity.toFloat(): Float
+    fun Float.toOffset(): Offset
+    fun Float.toVelocity(): Velocity
+}
+
+fun SpaceVectorConverter(orientation: Orientation) =
+    when (orientation) {
+        Orientation.Horizontal -> HorizontalConverter
+        Orientation.Vertical -> VerticalConverter
+    }
+
+private val HorizontalConverter =
+    object : SpaceVectorConverter {
+        override fun Offset.toFloat() = x
+        override fun Velocity.toFloat() = x
+        override fun Float.toOffset() = Offset(this, 0f)
+        override fun Float.toVelocity() = Velocity(this, 0f)
+    }
+
+private val VerticalConverter =
+    object : SpaceVectorConverter {
+        override fun Offset.toFloat() = y
+        override fun Velocity.toFloat() = y
+        override fun Float.toOffset() = Offset(0f, this)
+        override fun Float.toVelocity() = Velocity(0f, this)
+    }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index cc7a0b8..ce3e1db 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -427,4 +428,30 @@
         assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
         assertThat(fooElement.sceneValues).isEmpty()
     }
+
+    @Test
+    fun existingElementsDontRecomposeWhenTransitionStateChanges() {
+        var fooCompositions = 0
+
+        rule.testTransition(
+            fromSceneContent = {
+                SideEffect { fooCompositions++ }
+                Box(Modifier.element(TestElements.Foo))
+            },
+            toSceneContent = {},
+            transition = {
+                spec = tween(4 * 16)
+
+                scaleSize(TestElements.Foo, width = 2f, height = 0.5f)
+                translate(TestElements.Foo, x = 10.dp, y = 10.dp)
+                fade(TestElements.Foo)
+            }
+        ) {
+            before { assertThat(fooCompositions).isEqualTo(1) }
+            at(16) { assertThat(fooCompositions).isEqualTo(1) }
+            at(32) { assertThat(fooCompositions).isEqualTo(1) }
+            at(48) { assertThat(fooCompositions).isEqualTo(1) }
+            after { assertThat(fooCompositions).isEqualTo(1) }
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
new file mode 100644
index 0000000..ce7db80
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.view.viewmodel
+
+import android.app.smartspace.SmartspaceTarget
+import android.provider.Settings
+import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalEditModeViewModelTest : SysuiTestCase() {
+    @Mock private lateinit var mediaHost: MediaHost
+
+    private lateinit var testScope: TestScope
+
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
+    private lateinit var widgetRepository: FakeCommunalWidgetRepository
+    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var mediaRepository: FakeCommunalMediaRepository
+
+    private lateinit var underTest: CommunalEditModeViewModel
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+
+        val withDeps = CommunalInteractorFactory.create()
+        keyguardRepository = withDeps.keyguardRepository
+        communalRepository = withDeps.communalRepository
+        tutorialRepository = withDeps.tutorialRepository
+        widgetRepository = withDeps.widgetRepository
+        smartspaceRepository = withDeps.smartspaceRepository
+        mediaRepository = withDeps.mediaRepository
+
+        underTest =
+            CommunalEditModeViewModel(
+                withDeps.communalInteractor,
+                mediaHost,
+            )
+    }
+
+    @Test
+    fun communalContent_onlyWidgetsAreShownInEditMode() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // Smartspace available.
+            val target = Mockito.mock(SmartspaceTarget::class.java)
+            whenever(target.smartspaceTargetId).thenReturn("target")
+            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+
+            // Media playing.
+            mediaRepository.mediaPlaying.value = true
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // Only Widgets are shown.
+            assertThat(communalContent?.size).isEqualTo(2)
+            assertThat(communalContent?.get(0))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(1))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
new file mode 100644
index 0000000..32f4d07
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.view.viewmodel
+
+import android.app.smartspace.SmartspaceTarget
+import android.provider.Settings
+import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalViewModelTest : SysuiTestCase() {
+    @Mock private lateinit var mediaHost: MediaHost
+
+    private lateinit var testScope: TestScope
+
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
+    private lateinit var widgetRepository: FakeCommunalWidgetRepository
+    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var mediaRepository: FakeCommunalMediaRepository
+
+    private lateinit var underTest: CommunalViewModel
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+
+        val withDeps = CommunalInteractorFactory.create()
+        keyguardRepository = withDeps.keyguardRepository
+        communalRepository = withDeps.communalRepository
+        tutorialRepository = withDeps.tutorialRepository
+        widgetRepository = withDeps.widgetRepository
+        smartspaceRepository = withDeps.smartspaceRepository
+        mediaRepository = withDeps.mediaRepository
+
+        underTest =
+            CommunalViewModel(
+                withDeps.communalInteractor,
+                withDeps.tutorialInteractor,
+                mediaHost,
+            )
+    }
+
+    @Test
+    fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial not started.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(
+                Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+            )
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            assertThat(communalContent!!).isNotEmpty()
+            communalContent!!.forEach { model ->
+                assertThat(model is CommunalContentModel.Tutorial).isTrue()
+            }
+        }
+
+    @Test
+    fun ordering_smartspaceBeforeUmoBeforeWidgets() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // Smartspace available.
+            val target = Mockito.mock(SmartspaceTarget::class.java)
+            whenever(target.smartspaceTargetId).thenReturn("target")
+            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+
+            // Media playing.
+            mediaRepository.mediaPlaying.value = true
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // Order is smart space, then UMO, then widget content.
+            assertThat(communalContent?.size).isEqualTo(4)
+            assertThat(communalContent?.get(0))
+                .isInstanceOf(CommunalContentModel.Smartspace::class.java)
+            assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
+            assertThat(communalContent?.get(2))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(3))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
new file mode 100644
index 0000000..61b2057
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.fold.ui.helper
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowLayoutInfo
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FoldPostureTest : SysuiTestCase() {
+
+    @Test
+    fun foldPosture_whenNull_returnsFolded() {
+        assertThat(foldPostureInternal(null)).isEqualTo(FoldPosture.Folded)
+    }
+
+    @Test
+    fun foldPosture_whenHalfOpenHorizontally_returnsTabletop() {
+        assertThat(
+                foldPostureInternal(
+                    createWindowLayoutInfo(
+                        state = FoldingFeature.State.HALF_OPENED,
+                        orientation = FoldingFeature.Orientation.HORIZONTAL,
+                    )
+                )
+            )
+            .isEqualTo(FoldPosture.Tabletop)
+    }
+
+    @Test
+    fun foldPosture_whenHalfOpenVertically_returnsBook() {
+        assertThat(
+                foldPostureInternal(
+                    createWindowLayoutInfo(
+                        state = FoldingFeature.State.HALF_OPENED,
+                        orientation = FoldingFeature.Orientation.VERTICAL,
+                    )
+                )
+            )
+            .isEqualTo(FoldPosture.Book)
+    }
+
+    @Test
+    fun foldPosture_whenFlatAndNotSeparating_returnsFullyUnfolded() {
+        assertThat(
+                foldPostureInternal(
+                    createWindowLayoutInfo(
+                        state = FoldingFeature.State.FLAT,
+                        orientation = FoldingFeature.Orientation.HORIZONTAL,
+                        isSeparating = false,
+                    )
+                )
+            )
+            .isEqualTo(FoldPosture.FullyUnfolded)
+    }
+
+    @Test
+    fun foldPosture_whenFlatAndSeparatingHorizontally_returnsTabletop() {
+        assertThat(
+                foldPostureInternal(
+                    createWindowLayoutInfo(
+                        state = FoldingFeature.State.FLAT,
+                        isSeparating = true,
+                        orientation = FoldingFeature.Orientation.HORIZONTAL,
+                    )
+                )
+            )
+            .isEqualTo(FoldPosture.Tabletop)
+    }
+
+    @Test
+    fun foldPosture_whenFlatAndSeparatingVertically_returnsBook() {
+        assertThat(
+                foldPostureInternal(
+                    createWindowLayoutInfo(
+                        state = FoldingFeature.State.FLAT,
+                        isSeparating = true,
+                        orientation = FoldingFeature.Orientation.VERTICAL,
+                    )
+                )
+            )
+            .isEqualTo(FoldPosture.Book)
+    }
+
+    private fun createWindowLayoutInfo(
+        state: FoldingFeature.State,
+        orientation: FoldingFeature.Orientation = FoldingFeature.Orientation.VERTICAL,
+        isSeparating: Boolean = false,
+        occlusionType: FoldingFeature.OcclusionType = FoldingFeature.OcclusionType.NONE,
+    ): WindowLayoutInfo {
+        return WindowLayoutInfo(
+            listOf(
+                object : FoldingFeature {
+                    override val bounds: Rect = Rect(0, 0, 100, 100)
+                    override val isSeparating: Boolean = isSeparating
+                    override val occlusionType: FoldingFeature.OcclusionType = occlusionType
+                    override val orientation: FoldingFeature.Orientation = orientation
+                    override val state: FoldingFeature.State = state
+                }
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
new file mode 100644
index 0000000..7b2ac90
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.flashlight.domain
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FlashlightMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileConfig = kosmos.qsFlashlightTileConfig
+    private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) }
+
+    @Test
+    fun mapsDisabledDataToInactiveState() {
+        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+
+        val actualActivationState = tileState.activationState
+
+        assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState)
+    }
+
+    @Test
+    fun mapsEnabledDataToActiveState() {
+        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+
+        val actualActivationState = tileState.activationState
+        assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
+    }
+
+    @Test
+    fun mapsEnabledDataToOnIconState() {
+        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null)
+
+        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+
+        val actualIcon = tileState.icon()
+        assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun mapsDisabledDataToOffIconState() {
+        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null)
+
+        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+
+        val actualIcon = tileState.icon()
+        assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun supportsOnlyClickAction() {
+        val dontCare = true
+        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+
+        val supportedActions = tileState.supportedActions
+        assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
new file mode 100644
index 0000000..00572d3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FlashlightTileDataInteractorTest : SysuiTestCase() {
+    private lateinit var controller: FakeFlashlightController
+    private lateinit var underTest: FlashlightTileDataInteractor
+
+    @Before
+    fun setup() {
+        controller = FakeFlashlightController(LeakCheck())
+        underTest = FlashlightTileDataInteractor(controller)
+    }
+
+    @Test
+    fun availabilityOnMatchesController() = runTest {
+        controller.hasFlashlight = true
+
+        runCurrent()
+        val availability by collectLastValue(underTest.availability(TEST_USER))
+
+        assertThat(availability).isTrue()
+    }
+    @Test
+    fun availabilityOffMatchesController() = runTest {
+        controller.hasFlashlight = false
+
+        runCurrent()
+        val availability by collectLastValue(underTest.availability(TEST_USER))
+
+        assertThat(availability).isFalse()
+    }
+
+    @Test
+    fun dataMatchesController() = runTest {
+        controller.setFlashlight(false)
+        val flowValues: List<FlashlightTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.setFlashlight(true)
+        runCurrent()
+        controller.setFlashlight(false)
+        runCurrent()
+
+        assertThat(flowValues.size).isEqualTo(3)
+        assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..f819f53
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.app.ActivityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FlashlightTileUserActionInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var controller: FlashlightController
+
+    private lateinit var underTest: FlashlightTileUserActionInteractor
+
+    @Before
+    fun setup() {
+        controller = mock<FlashlightController>()
+        underTest = FlashlightTileUserActionInteractor(controller)
+    }
+
+    @Test
+    fun handleClickToEnable() = runTest {
+        assumeFalse(ActivityManager.isUserAMonkey())
+        val stateBeforeClick = false
+
+        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+
+        verify(controller).setFlashlight(!stateBeforeClick)
+    }
+
+    @Test
+    fun handleClickToDisable() = runTest {
+        assumeFalse(ActivityManager.isUserAMonkey())
+        val stateBeforeClick = true
+
+        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+
+        verify(controller).setFlashlight(!stateBeforeClick)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
new file mode 100644
index 0000000..8791877
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.qs.tiles.impl.location.domain
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.google.common.truth.Truth
+import junit.framework.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LocationTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileConfig = kosmos.qsLocationTileConfig
+
+    private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) }
+
+    @Test
+    fun mapsDisabledDataToInactiveState() {
+        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
+
+        val actualActivationState = tileState.activationState
+        Assert.assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState)
+    }
+
+    @Test
+    fun mapsEnabledDataToActiveState() {
+        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
+
+        val actualActivationState = tileState.activationState
+        Assert.assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
+    }
+
+    @Test
+    fun mapsEnabledDataToOnIconState() {
+        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null)
+
+        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
+
+        val actualIcon = tileState.icon()
+        Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun mapsDisabledDataToOffIconState() {
+        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null)
+
+        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
+
+        val actualIcon = tileState.icon()
+        Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun supportsClickAndLongClickActions() {
+        val dontCare = true
+
+        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(dontCare))
+
+        val supportedActions = tileState.supportedActions
+        Truth.assertThat(supportedActions)
+            .containsExactly(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
new file mode 100644
index 0000000..8fdc93b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.qs.tiles.impl.location.interactor
+
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LocationTileDataInteractorTest : SysuiTestCase() {
+    private lateinit var controller: FakeLocationController
+    private lateinit var underTest: LocationTileDataInteractor
+
+    @Before
+    fun setup() {
+        controller = FakeLocationController(LeakCheck())
+        underTest = LocationTileDataInteractor(controller)
+    }
+
+    @Test
+    fun isAvailableRegardlessOfController() = runTest {
+        controller.setLocationEnabled(false)
+
+        runCurrent()
+        val availability by collectLastValue(underTest.availability(TEST_USER))
+
+        Truth.assertThat(availability).isTrue()
+    }
+
+    @Test
+    fun dataMatchesController() = runTest {
+        controller.setLocationEnabled(false)
+        val flowValues: List<LocationTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.setLocationEnabled(true)
+        runCurrent()
+        controller.setLocationEnabled(false)
+        runCurrent()
+
+        Truth.assertThat(flowValues.size).isEqualTo(3)
+        Truth.assertThat(flowValues.map { it.isEnabled })
+            .containsExactly(false, true, false)
+            .inOrder()
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..0fb8ae6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.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.qs.tiles.impl.location.interactor
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
+import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.policy.LocationController
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LocationTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val keyguardController = FakeKeyguardStateController()
+
+    private lateinit var underTest: LocationTileUserActionInteractor
+
+    @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val kosmos = Kosmos()
+        underTest =
+            LocationTileUserActionInteractor(
+                EmptyCoroutineContext,
+                kosmos.testScope,
+                locationController,
+                qsTileIntentUserActionHandler,
+                activityStarter,
+                keyguardController,
+            )
+    }
+
+    @Test
+    fun handleClickToEnable() = runTest {
+        val stateBeforeClick = false
+
+        underTest.handleInput(click(LocationTileModel(stateBeforeClick)))
+
+        Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick)
+    }
+
+    @Test
+    fun handleClickToDisable() = runTest {
+        val stateBeforeClick = true
+
+        underTest.handleInput(click(LocationTileModel(stateBeforeClick)))
+
+        Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick)
+    }
+
+    @Test
+    fun handleLongClick() = runTest {
+        val dontCare = true
+
+        underTest.handleInput(longClick(LocationTileModel(dontCare)))
+
+        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+        val actualIntentAction = intentInput.intent.action
+        val expectedIntentAction = Settings.ACTION_LOCATION_SOURCE_SETTINGS
+        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
new file mode 100644
index 0000000..f04dfd1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+            featureFlagsClassic.apply {
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                set(Flags.NSSL_DEBUG_LINES, false)
+            }
+        }
+    private val testScope = kosmos.testScope
+    private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel
+    private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel
+    private val sceneInteractor = kosmos.sceneInteractor
+
+    @Test
+    fun updateBounds() =
+        testScope.runTest {
+            val bounds by collectLastValue(appearanceViewModel.stackBounds)
+
+            val top = 200f
+            val bottom = 550f
+            placeholderViewModel.onBoundsChanged(top, bottom)
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
+        }
+
+    @Test
+    fun updateShadeExpansion() =
+        testScope.runTest {
+            val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+            assertThat(expandFraction).isEqualTo(0f)
+
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+            val transitionProgress = MutableStateFlow(0f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Lockscreen,
+                    toScene = SceneKey.Shade,
+                    progress = transitionProgress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            val steps = 10
+            repeat(steps) { repetition ->
+                val progress = (1f / steps) * (repetition + 1)
+                transitionProgress.value = progress
+                runCurrent()
+                assertThat(expandFraction).isWithin(0.01f).of(progress)
+            }
+
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            assertThat(expandFraction).isWithin(0.01f).of(1f)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
new file mode 100644
index 0000000..c7411cd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.notificationStackAppearanceInteractor
+
+    @Test
+    fun stackBounds() =
+        testScope.runTest {
+            val stackBounds by collectLastValue(underTest.stackBounds)
+
+            val bounds1 =
+                NotificationContainerBounds(
+                    top = 100f,
+                    bottom = 200f,
+                    isAnimated = true,
+                )
+            underTest.setStackBounds(bounds1)
+            assertThat(stackBounds).isEqualTo(bounds1)
+
+            val bounds2 =
+                NotificationContainerBounds(
+                    top = 200f,
+                    bottom = 300f,
+                    isAnimated = false,
+                )
+            underTest.setStackBounds(bounds2)
+            assertThat(stackBounds).isEqualTo(bounds2)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun setStackBounds_withImproperBounds_throwsException() =
+        testScope.runTest {
+            underTest.setStackBounds(
+                NotificationContainerBounds(
+                    top = 100f,
+                    bottom = 99f,
+                )
+            )
+        }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index f0e3c99..6434209 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -133,16 +133,6 @@
             boolean afterKeyguardGone,
             boolean deferred);
 
-    /** Execute a runnable after dismissing keyguard. */
-    void executeRunnableDismissingKeyguard(
-            Runnable runnable,
-            Runnable cancelAction,
-            boolean dismissShade,
-            boolean afterKeyguardGone,
-            boolean deferred,
-            boolean willAnimateOnKeyguard,
-            @Nullable String customMessage);
-
     /** Whether we should animate an activity launch. */
     boolean shouldAnimateLaunch(boolean isActivityIntent);
 
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 2187352..7b90270 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -22,7 +22,8 @@
     android:focusable="true"
     android:clickable="true"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:visibility="invisible">
 
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/alternate_bouncer_scrim"
diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
index 952f056..cc99f5e 100644
--- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
+++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
@@ -22,13 +22,15 @@
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:gravity="center_vertical"
-    android:orientation="horizontal" >
+    android:orientation="horizontal"
+    android:theme="@style/Theme.SystemUI.QuickSettings.Header" >
 
     <com.android.systemui.util.AutoMarqueeTextView
         android:id="@+id/mobile_carrier_text"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
+        android:textAppearance="@style/TextAppearance.QS.Status.Carriers"
         android:layout_marginEnd="@dimen/qs_carrier_margin_width"
         android:visibility="gone"
         android:textDirection="locale"
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/res/anim/instant_fade_out.xml
similarity index 68%
rename from packages/SystemUI/communal/layout/AndroidManifest.xml
rename to packages/SystemUI/res/anim/instant_fade_out.xml
index 141be07..800420b 100644
--- a/packages/SystemUI/communal/layout/AndroidManifest.xml
+++ b/packages/SystemUI/res/anim/instant_fade_out.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2023 The Android Open Source Project
+<!--
+    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.
@@ -15,4 +15,9 @@
     limitations under the License.
 -->
 
-<manifest package="com.android.systemui.communal.layout" />
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+    android:interpolator="@android:interpolator/linear_out_slow_in"
+    android:duration="0"/>
+
diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
deleted file mode 100644
index bc1775e..0000000
--- a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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.
-  -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:id="@id/background"
-        android:gravity="center">
-        <shape android:shape="oval">
-            <size
-                android:height="24px"
-                android:width="24px"
-                />
-            <solid android:color="#FFFFFFFF" />
-        </shape>
-    </item>
-    <item android:id="@id/icon"
-        android:gravity="center"
-        android:width="20px"
-        android:height="20px"
-        android:drawable="@drawable/ic_person_outline"
-        />
-</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml
new file mode 100644
index 0000000..a1e8b9d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <group>
+        <path
+            android:pathData="M12 8.13333C12.7089 8.13333 13.2889 8.71333 13.2889 9.42221C13.2889 10.1311 12.7089 10.7111 12 10.7111C11.2911 10.7111 10.7111 10.1311 10.7111 9.42221C10.7111 8.71333 11.2911 8.13333 12 8.13333Z"
+            android:fillColor="#FFFFFF"
+            />
+        <path
+            android:pathData="M12 13.9333C13.74 13.9333 15.7378 14.7647 15.8667 15.2222V15.8667H8.13333V15.2287C8.26221 14.7647 10.26 13.9333 12 13.9333Z"
+            android:fillColor="#FFFFFF"
+            />
+        <path
+            android:pathData="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM9.42228 9.42224C9.42228 7.99802 10.5758 6.84447 12.0001 6.84447C13.4243 6.84447 14.5778 7.99802 14.5778 9.42224C14.5778 10.8465 13.4243 12 12.0001 12C10.5758 12 9.42228 10.8465 9.42228 9.42224ZM12 12.6445C10.2794 12.6445 6.84447 13.508 6.84447 15.2223V17.1556H17.1556V15.2223C17.1556 13.508 13.7207 12.6445 12 12.6445Z"
+            android:fillColor="#FFFFFF"
+            android:fillType="evenOdd"
+            />
+    </group>
+</vector>
+
+
diff --git a/packages/SystemUI/res/drawable/ic_person_outline.xml b/packages/SystemUI/res/drawable/ic_person_outline.xml
deleted file mode 100644
index d94714e..0000000
--- a/packages/SystemUI/res/drawable/ic_person_outline.xml
+++ /dev/null
@@ -1,26 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="960"
-    android:viewportHeight="960"
-    android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/black"
-        android:pathData="M480,480Q414,480 367,433Q320,386 320,320Q320,254 367,207Q414,160 480,160Q546,160 593,207Q640,254 640,320Q640,386 593,433Q546,480 480,480ZM160,800L160,688Q160,654 177.5,625.5Q195,597 224,582Q286,551 350,535.5Q414,520 480,520Q546,520 610,535.5Q674,551 736,582Q765,597 782.5,625.5Q800,654 800,688L800,800L160,800ZM240,720L720,720L720,688Q720,677 714.5,668Q709,659 700,654Q646,627 591,613.5Q536,600 480,600Q424,600 369,613.5Q314,627 260,654Q251,659 245.5,668Q240,677 240,688L240,720ZM480,400Q513,400 536.5,376.5Q560,353 560,320Q560,287 536.5,263.5Q513,240 480,240Q447,240 423.5,263.5Q400,287 400,320Q400,353 423.5,376.5Q447,400 480,400ZM480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320ZM480,720L480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720L480,720L480,720Z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ksh_key_item_background.xml b/packages/SystemUI/res/drawable/ksh_key_item_background.xml
index 75ff30d..1db48fa 100644
--- a/packages/SystemUI/res/drawable/ksh_key_item_background.xml
+++ b/packages/SystemUI/res/drawable/ksh_key_item_background.xml
@@ -17,5 +17,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
         android:shape="rectangle">
     <solid android:color="@color/ksh_key_item_background" />
-    <corners android:radius="2dp" />
+    <corners android:radius="8dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
index 34e7d0a..f7c04b5 100644
--- a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
@@ -26,10 +26,17 @@
         </shape>
     </item>
     <item
-        android:drawable="@drawable/ic_ksh_key_down"
-        android:gravity="end|center_vertical"
-        android:textColor="?androidprv:attr/textColorPrimary"
-        android:width="@dimen/screenrecord_spinner_arrow_size"
-        android:height="@dimen/screenrecord_spinner_arrow_size"
-        android:end="20dp" />
+        android:end="20dp"
+        android:gravity="end|center_vertical">
+        <vector
+            android:width="@dimen/screenrecord_spinner_arrow_size"
+            android:height="@dimen/screenrecord_spinner_arrow_size"
+            android:viewportWidth="24"
+            android:viewportHeight="24"
+            android:tint="?androidprv:attr/colorControlNormal">
+            <path
+                android:fillColor="#FF000000"
+                android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+        </vector>
+    </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_button_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
index a21489c..bf90853 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
@@ -22,8 +22,8 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="16dp"/>
-                <solid android:color="?androidprv:attr/colorSurface"/>
+                <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
             </shape>
         </item>
     </ripple>
-</inset>
\ No newline at end of file
+</inset>
diff --git a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
index 2ff32b6..f692ed97 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
@@ -22,8 +22,8 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="16dp"/>
-                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <solid android:color="?androidprv:attr/materialColorPrimary"/>
             </shape>
         </item>
     </ripple>
-</inset>
\ No newline at end of file
+</inset>
diff --git a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
new file mode 100644
index 0000000..6c4d4fb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<inset  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<ripple android:color="?android:attr/colorControlHighlight" android:radius="24dp">
+  <item>
+    <shape android:shape="oval">
+      <size android:width="24dp"
+        android:height="24dp" />
+      <solid android:color="?androidprv:attr/colorSurface"/>
+    </shape>
+  </item>
+</ripple>
+</inset>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index af29cad..50241cd 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -111,107 +111,57 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
-                app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
+                app:layout_constraintBottom_toTopOf="@+id/see_all_button" />
 
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/see_all_layout_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                app:constraint_referenced_ids="ic_arrow,see_all_text" />
-
-            <View
-                android:id="@+id/see_all_clickable_row"
+            <Button
+                android:id="@+id/see_all_button"
+                style="@style/BluetoothTileDialog.Device"
+                android:paddingEnd="0dp"
+                android:paddingStart="20dp"
+                android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
-                android:layout_height="0dp"
+                android:layout_height="64dp"
                 android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/device_list"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" />
-
-            <ImageView
-                android:id="@+id/ic_arrow"
-                android:layout_marginStart="36dp"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:importantForAccessibility="no"
-                android:gravity="center_vertical"
-                android:src="@drawable/ic_arrow_forward"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toStartOf="@id/see_all_text"
-                app:layout_constraintTop_toBottomOf="@id/device_list" />
-
-            <TextView
-                android:id="@+id/see_all_text"
-                style="@style/BluetoothTileDialog.Device"
-                android:layout_width="0dp"
-                android:layout_height="64dp"
-                android:maxLines="1"
-                android:ellipsize="end"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:clickable="false"
-                android:layout_marginStart="0dp"
-                android:paddingStart="20dp"
+                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button"
+                android:drawableStart="@drawable/ic_arrow_forward"
+                android:drawablePadding="20dp"
+                android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/see_all_bluetooth_devices"
                 android:textSize="14sp"
                 android:textAppearance="@style/TextAppearance.Dialog.Title"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
-                app:layout_constraintStart_toEndOf="@+id/ic_arrow"
-                app:layout_constraintTop_toBottomOf="@id/device_list"
-                app:layout_constraintEnd_toEndOf="parent" />
+                android:textDirection="locale"
+                android:textAlignment="viewStart"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:visibility="gone" />
 
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/pair_new_device_layout_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                app:constraint_referenced_ids="ic_add,pair_new_device_text" />
-
-            <View
-                android:id="@+id/pair_new_device_clickable_row"
+            <Button
+                android:id="@+id/pair_new_device_button"
+                style="@style/BluetoothTileDialog.Device"
+                android:paddingEnd="0dp"
+                android:paddingStart="20dp"
+                android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
-                android:layout_height="0dp"
+                android:layout_height="64dp"
                 android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/see_all_text"
-                app:layout_constraintBottom_toTopOf="@+id/done_button" />
-
-            <ImageView
-                android:id="@+id/ic_add"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_marginStart="36dp"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:src="@drawable/ic_add"
-                app:layout_constraintBottom_toTopOf="@id/done_button"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
-                app:layout_constraintTop_toBottomOf="@id/see_all_text"
-                android:tint="?android:attr/textColorPrimary" />
-
-            <TextView
-                android:id="@+id/pair_new_device_text"
-                style="@style/BluetoothTileDialog.Device"
-                android:layout_width="0dp"
-                android:layout_height="64dp"
-                android:maxLines="1"
-                android:ellipsize="end"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:clickable="false"
-                android:layout_marginStart="0dp"
-                android:paddingStart="20dp"
+                app:layout_constraintTop_toBottomOf="@+id/see_all_button"
+                app:layout_constraintBottom_toTopOf="@+id/done_button"
+                android:drawableStart="@drawable/ic_add"
+                android:drawablePadding="20dp"
+                android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/pair_new_bluetooth_devices"
                 android:textSize="14sp"
                 android:textAppearance="@style/TextAppearance.Dialog.Title"
-                app:layout_constraintStart_toEndOf="@+id/ic_add"
-                app:layout_constraintTop_toBottomOf="@id/see_all_text"
-                app:layout_constraintEnd_toEndOf="parent" />
+                android:textDirection="locale"
+                android:textAlignment="viewStart"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:visibility="gone" />
 
             <Button
                 android:id="@+id/done_button"
@@ -227,7 +177,7 @@
                 android:maxLines="1"
                 android:text="@string/inline_done_button"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+                app:layout_constraintTop_toBottomOf="@id/pair_new_device_button"
                 app:layout_constraintBottom_toBottomOf="parent" />
         </androidx.constraintlayout.widget.ConstraintLayout>
     </androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index ad9a775..ec2edb5 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -113,7 +113,7 @@
             android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
-            android:src="@drawable/dream_overlay_assistant_attention_indicator"
+            android:src="@drawable/ic_assistant_attention_indicator"
             android:visibility="gone"
             android:contentDescription="@string/assistant_attention_content_description" />
 
diff --git a/packages/SystemUI/res/layout/edit_widgets.xml b/packages/SystemUI/res/layout/edit_widgets.xml
deleted file mode 100644
index 182e651..0000000
--- a/packages/SystemUI/res/layout/edit_widgets.xml
+++ /dev/null
@@ -1,32 +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.
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/edit_widgets"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <Button
-        style="@android:Widget.DeviceDefault.Button.Colored"
-        android:id="@+id/add_widget"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:textSize="28sp"
-        android:text="@string/hub_mode_add_widget_button_text"/>
-
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index fcf9638..a005100 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -22,8 +22,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="48dp"
-        android:paddingStart="24dp"
-        android:paddingEnd="24dp"
         android:paddingBottom="8dp">
     <ImageView
             android:id="@+id/keyboard_shortcuts_icon"
@@ -57,5 +55,6 @@
             android:layout_alignParentEnd="true"
             android:textSize="14sp"
             android:scrollHorizontally="false"
-            android:layout_centerVertical="true"/>
+            android:layout_centerVertical="true"
+            android:padding="0dp" />
 </com.android.systemui.statusbar.KeyboardShortcutAppItemLayout>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
index 0759990..4f100f6 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
@@ -21,7 +21,5 @@
           android:textSize="14sp"
           android:fontFamily="sans-serif-medium"
           android:importantForAccessibility="yes"
-          android:paddingStart="24dp"
           android:paddingTop="20dp"
-          android:paddingEnd="24dp"
           android:paddingBottom="10dp"/>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml
index a3901d0..f96edbf 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml
@@ -18,7 +18,12 @@
 <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="@dimen/ksh_item_padding"
+        android:minWidth="48dp"
+        android:minHeight="32dp"
+        android:paddingLeft="@dimen/ksh_key_view_padding_horizontal"
+        android:paddingRight="@dimen/ksh_key_view_padding_horizontal"
+        android:paddingTop="@dimen/ksh_key_view_padding_vertical"
+        android:paddingBottom="@dimen/ksh_key_view_padding_vertical"
         android:layout_marginStart="@dimen/ksh_item_margin_start"
-        android:scaleType="fitXY"
+        android:scaleType="matrix"
         android:background="@drawable/ksh_key_item_background" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml
deleted file mode 100644
index a037cb2..0000000
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-           android:layout_width="wrap_content"
-           android:layout_height="wrap_content"
-           android:padding="@dimen/ksh_item_padding"
-           android:layout_marginLeft="0dp"
-           android:layout_marginRight="0dp"
-           android:scaleType="fitXY"
-           android:tint="?android:attr/textColorPrimary"
-           style="@style/ShortcutItemBackground" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml
deleted file mode 100644
index 12b4e15..0000000
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:padding="@dimen/ksh_item_padding"
-          android:layout_marginStart="@dimen/ksh_item_margin_start"
-          style="@style/ShortcutItemBackground"
-          android:textColor="?android:attr/textColorPrimary"
-          android:singleLine="false"
-          android:gravity="center"
-          android:textSize="@dimen/ksh_item_text_size"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml
deleted file mode 100644
index 727f2c1..0000000
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:padding="@dimen/ksh_item_padding"
-          android:layout_marginLeft="0dp"
-          android:layout_marginRight="0dp"
-          android:text="+"
-          style="@style/ShortcutItemBackground"
-          android:textColor="?android:attr/textColorPrimary"
-          android:singleLine="true"
-          android:gravity="center"
-          android:textSize="@dimen/ksh_item_text_size"
-          android:textAllCaps="true"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_separator_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_separator_view.xml
new file mode 100644
index 0000000..8772a73
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_separator_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:minHeight="32dp"
+    android:padding="@dimen/ksh_item_padding"
+    android:layout_marginLeft="0dp"
+    android:layout_marginRight="0dp"
+    android:text="@string/keyboard_shortcut_join"
+    android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+    android:singleLine="true"
+    android:gravity="center"
+    android:textSize="@dimen/ksh_item_text_size" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml
deleted file mode 100644
index 00ef947..0000000
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:padding="@dimen/ksh_item_padding"
-          android:layout_marginLeft="0dp"
-          android:layout_marginRight="0dp"
-          android:text="|"
-          style="@style/ShortcutItemBackground"
-          android:textColor="?android:attr/textColorPrimary"
-          android:singleLine="true"
-          android:gravity="center"
-          android:textSize="@dimen/ksh_item_text_size"
-          android:textAllCaps="true"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
index b06f7fc..42bbf25 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
@@ -14,14 +14,18 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:padding="@dimen/ksh_item_padding"
-          android:layout_marginStart="@dimen/ksh_item_margin_start"
-          android:background="@drawable/ksh_key_item_background"
-          android:textColor="@color/ksh_key_item_color"
-          android:singleLine="true"
-          android:gravity="center"
-          android:textSize="@dimen/ksh_item_text_size"
-          android:textAllCaps="true"/>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minWidth="32dp"
+        android:minHeight="32dp"
+        android:paddingLeft="@dimen/ksh_key_view_padding_horizontal"
+        android:paddingRight="@dimen/ksh_key_view_padding_horizontal"
+        android:paddingTop="@dimen/ksh_key_view_padding_vertical"
+        android:paddingBottom="@dimen/ksh_key_view_padding_vertical"
+        android:layout_marginStart="@dimen/ksh_item_margin_start"
+        android:background="@drawable/ksh_key_item_background"
+        android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+        android:singleLine="true"
+        android:gravity="center"
+        android:textSize="@dimen/ksh_item_text_size" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 13425c9..61f69c0 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -53,7 +53,7 @@
             android:hint="@string/keyboard_shortcut_search_list_hint"
             android:textColorHint="?android:attr/textColorTertiary" />
 
-        <ImageView
+        <ImageButton
             android:id="@+id/keyboard_shortcuts_search_cancel"
             android:layout_gravity="center_vertical|end"
             android:layout_width="wrap_content"
@@ -61,36 +61,34 @@
             android:layout_marginEnd="49dp"
             android:padding="16dp"
             android:contentDescription="@string/keyboard_shortcut_clear_text"
-            android:src="@drawable/ic_shortcutlist_search_button_cancel" />
+            android:src="@drawable/ic_shortcutlist_search_button_cancel"
+            android:background="@drawable/shortcut_search_cancel_button"
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:pointerIcon="arrow" />
     </FrameLayout>
 
     <HorizontalScrollView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginStart="49dp"
+        android:layout_marginEnd="0dp"
         android:scrollbars="none">
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="center_vertical"
             android:orientation="horizontal">
-            <View
-                android:layout_width="0dp"
-                android:layout_height="0dp"
-                android:layout_marginStart="37dp"/>
-
             <Button
                 android:id="@+id/shortcut_system"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="12dp"
                 style="@style/ShortCutButton"
-                android:text="@string/keyboard_shortcut_search_category_system"/>
+                android:text="@string/keyboard_shortcut_search_category_system" />
 
             <Button
                 android:id="@+id/shortcut_input"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="12dp"
                 style="@style/ShortCutButton"
                 android:text="@string/keyboard_shortcut_search_category_input"/>
 
@@ -98,7 +96,6 @@
                 android:id="@+id/shortcut_open_apps"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="12dp"
                 style="@style/ShortCutButton"
                 android:text="@string/keyboard_shortcut_search_category_open_apps"/>
 
@@ -106,7 +103,6 @@
                 android:id="@+id/shortcut_specific_app"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="12dp"
                 style="@style/ShortCutButton"
                 android:text="@string/keyboard_shortcut_search_category_current_app"/>
         </LinearLayout>
@@ -117,6 +113,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="50dp"
+        android:layout_marginStart="49dp"
+        android:layout_marginEnd="49dp"
         android:layout_gravity="center_horizontal"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textColor="?android:attr/textColorPrimary"
@@ -127,8 +125,10 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="16dp"
-        android:layout_marginStart="25dp"
-        android:layout_marginEnd="25dp">
+        android:layout_marginStart="49dp"
+        android:layout_marginEnd="49dp"
+        android:overScrollMode="never"
+        android:layout_marginBottom="16dp">
         <LinearLayout
             android:id="@+id/keyboard_shortcuts_container"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml
index 7aba1cf..9e54ab1 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml
@@ -22,6 +22,8 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginEnd="24dp"
         android:orientation="vertical">
         <ScrollView
             android:id="@+id/keyboard_shortcuts_scroll_view"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b8f4c0f..7ab44e7 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -53,6 +53,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:layout_gravity="center_vertical"
+                android:focusable="true"
+                android:importantForAccessibility="no"
                 android:tint="?attr/shadeActive"
                 android:visibility="gone" />
 
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
index 130472d..02c8c3a 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
@@ -17,7 +17,8 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="250dp"
-    android:layout_height="48dp"
+    android:layout_height="wrap_content"
+    android:minHeight="48dp"
     android:orientation="vertical"
     android:padding="12dp">
     <TextView
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index d9f4b79..8916e42 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -47,6 +47,7 @@
             android:layout_weight="0"
             android:layout_gravity="end"
             android:id="@+id/screenrecord_audio_switch"
+            android:contentDescription="@string/screenrecord_audio_label"
             style="@style/ScreenRecord.Switch"
             android:importantForAccessibility="yes"/>
     </LinearLayout>
@@ -79,6 +80,7 @@
             android:minWidth="48dp"
             android:layout_height="48dp"
             android:id="@+id/screenrecord_taps_switch"
+            android:contentDescription="@string/screenrecord_taps_label"
             style="@style/ScreenRecord.Switch"
             android:importantForAccessibility="yes"/>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 7e03bd9..ca0fb85 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -30,7 +30,6 @@
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
     />
 
@@ -38,7 +37,6 @@
         android:id="@+id/scrim_notifications"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
     />
 
@@ -78,7 +76,6 @@
         android:id="@+id/scrim_in_front"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
     />
 
@@ -119,11 +116,6 @@
         android:inflatedId="@+id/multi_shade"
         android:layout="@layout/multi_shade" />
 
-    <include layout="@layout/alternate_bouncer"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible" />
-
     <com.android.systemui.biometrics.AuthRippleView
         android:id="@+id/auth_ripple"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/udfps_touch_overlay.xml b/packages/SystemUI/res/layout/udfps_touch_overlay.xml
new file mode 100644
index 0000000..ea92776
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_touch_overlay.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_touch_overlay"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/accessibility_fingerprint_label">
+</com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 33b241b..45a2e8a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-toestelikoon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik om toestelbesonderhede op te stel."</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik om alle toestelle te sien"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik om met nuwe toestel te koppel"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterypersentasie is onbekend."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Gekoppel aan <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterykrag"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swiep links om die gemeenskaplike tutoriaal te begin"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Maak die legstukredigeerder oop"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Verwyder ’n legstuk"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Voeg legstuk by"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Word gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Sleutelbordlig"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Vlak %1$d van %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index be58b52..bac72bf 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"የብሉቱዝ መሣሪያ አዶ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"የመሣሪያ ዝርዝርን ለማዋቀር ጠቅ ያድርጉ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ሁሉንም መሣሪያዎች ለማየት ጠቅ ያድርጉ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ከአዲስ መሣሪያ ጋር ለማጣመር ጠቅ ያድርጉ"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"የባትሪ መቶኛ አይታወቅም።"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"ከ<xliff:g id="CAST">%s</xliff:g> ጋር ተገናኝቷል።"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ብሉቱዝን ይጠቀሙ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ተገናኝቷል"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ተቀምጧል"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ባትሪ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"የጋራ አጋዥ ሥልጠናውን ለመጀመር ወደ ግራ ያንሸራትቱ።"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"የምግብር አርታዒውን ይክፈቱ"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ምግብርን አስወግድ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ምግብር አክል"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ጥቅም ላይ ውሏል"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ጥቅም ላይ እየዋለ ነው"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ጥቅም ላይ ውሏል"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"የቁልፍ ሰሌዳ የጀርባ ብርሃን"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"ደረጃ %1$d ከ %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 2dbd3e9..c188535 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"رمز الجهاز الذي يتضمّن بلوتوث"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"انقر هنا لضبط إعدادات الجهاز."</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"انقر لعرض جميع الأجهزة."</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"انقر لإقران جهاز جديد."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"تم الاتصال بـ <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"استخدام البلوتوث"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متّصل"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"مستوى طاقة البطارية <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن سريعًا • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"مرِّر سريعًا لليمين لبدء الدليل التوجيهي العام."</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"فتح محرِّر التطبيقات المصغّرة"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"إزالة تطبيق مصغّر"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"إضافة تطبيق مصغّر"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"قيد الاستخدام في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"الإضاءة الخلفية للوحة المفاتيح"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"‏مستوى الإضاءة: %1$d من %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 9bad7b9..59eca3a 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ব্লুটুথ ডিভাইচৰ চিহ্ন"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ডিভাইচৰ সবিশেষ কনফিগাৰ কৰিবলৈ ক্লিক কৰক"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"আটাইবোৰ ডিভাইচ চাবলৈ ক্লিক কৰক"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"নতুন ডিভাইচ পেয়াৰ কৰিবলৈ ক্লিক কৰক"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"বেটাৰীৰ চাৰ্জৰ শতাংশ অজ্ঞাত।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>ত সংযোগ হ’ল।"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যৱহাৰ কৰক"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"সংযুক্ত আছে"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ছেভ কৰা হৈছে"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ বাওঁফালে ছোৱাইপ কৰক"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ৱিজেট সম্পাদকটো খোলক"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"এটা ৱিজেট আঁতৰাওক"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ৱিজেট যোগ দিয়ক"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"শেহতীয়াকৈ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)এ ব্যৱহাৰ কৰিছে"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)এ ব্যৱহাৰ কৰি আছে"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"শেহতীয়াকৈ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)এ ব্যৱহাৰ কৰিছে"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"কীব’ৰ্ডৰ বেকলাইট"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dৰ %1$d স্তৰ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index dfba8c2..121e276 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth cihazı ikonası"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cihaz təfərrüatlarını konfiqurasiya etmək üçün klikləyin"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Bütün cihazları görmək üçün klikləyin"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yeni cihazı birləşdirmək üçün klikləyin"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareyanın faizi naməlumdur."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> cihazına qoşulub."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth aç"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batareya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sürətlə şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"İcma təlimatını başlatmaq üçün sola sürüşdürün"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidcet redaktorunu açın"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Vidceti silin"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Vidcet əlavə edin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) istifadə edib"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edir"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edib"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatura işığı"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Səviyyə %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 4060a8d..4c535d8 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da biste konfigurisali detalje o uređaju"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite da biste videli sve uređaje"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite da biste uparili nov uređaj"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procenat napunjenosti baterije nije poznat."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani smo sa uređajem <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulevo da biste započeli zajednički vodič"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvori uređivač vidžeta"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Ukloni vidžet"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj vidžet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koriste <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Pozadinsko osvetljenje tastature"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d. nivo od %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index f5490010..ffde20e 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок прылады з Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Націсніце, каб задаць падрабязныя налады прылады"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Націсніце, каб пабачыць усе прылады"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Націсніце, каб спалучыць новую прыладу"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Працэнт зараду акумулятара невядомы."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Ёсць падключэнне да <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Выкарыстоўваць Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Падключана"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Захавана"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе хуткая зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Правядзіце пальцам па экране ўлева, каб азнаёміцца з дапаможнікам"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Адкрыць рэдактар віджэтаў"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Выдаліць віджэт"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Дадаць віджэт"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Зараз выкарыстоўваецца праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Падсветка клавіятуры"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Узровень %1$d з %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index ceb98a0..ad690e6 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона за устройство с Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликнете, за да конфигурирате подробностите за устройството"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликнете, за да видите всички устройства"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликнете за сдвояване на ново устройство"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентът на батерията е неизвестен."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Установена е връзка с/ъс <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Използване на Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Установена е връзка"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Запазено"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бързо • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Прекарайте пръст наляво, за да стартирате общия урок"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отваряне на редактора на приспособлението"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Премахване на приспособление"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Добавяне на приспособлението"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Използва се от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Подсветка на клавиатурата"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ниво %1$d от %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index c1ccba5..1383776 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ব্লুটুথ ডিভাইসের আইকন"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ডিভাইসের বিবরণ কনফিগার করতে ক্লিক করুন"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"সব ডিভাইস দেখতে ক্লিক করুন"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"নতুন ডিভাইস পেয়ার করতে ক্লিক করুন"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ব্যাটারি কত শতাংশ আছে তা জানা যায়নি।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> এর সাথে সংযুক্ত৷"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যবহার করুন"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"কানেক্ট করা আছে"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"চার্জ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্রুত চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"কমিউনিটি টিউটোরিয়াল চালু করতে বাঁদিকে সোয়াইপ করুন"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"উইজেট এডিটর খুলুন"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"উইজেট সরিয়ে দিন"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"উইজেট যোগ করুন"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ব্যবহার করা হয়েছে"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হচ্ছে"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হয়েছে"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"কীবোর্ড ব্যাকলাইট"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d-এর মধ্যে %1$d লেভেল"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 08656e5..0481288 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da konfigurirate detalje uređaja"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Pregled svih uređaja klikom"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Uparivanje novog uređaja klikom"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak napunjenosti baterije nije poznat"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezan na <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulijevo da pokrenete zajednički vodič"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje uređivača vidžeta"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Uklanjanje vidžeta"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodajte vidžet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno je koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koristi aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno je koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Pozadinsko osvjetljenje tastature"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d. nivo od %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 443c2ee..03ef98d 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona de dispositiu Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Fes clic per configurar els detalls del dispositiu"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Fes clic per veure tots els dispositius"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Fes clic per vincular un dispositiu nou"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Es desconeix el percentatge de bateria."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Està connectat amb <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza\'l"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Llisca cap a l\'esquerra per iniciar el tutorial de la comunitat"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Obre l\'editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Suprimeix un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Afegeix un widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En ús per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroil·luminació del teclat"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivell %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index ada9c3c..8b75a3e 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona zařízení Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknutím nakonfigurujete podrobnosti o zařízení"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknutím zobrazíte všechna zařízení"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknutím spárujete nové zařízení"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procento baterie není známé."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Jste připojeni k zařízení <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Rychlé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Přejetím doleva spustíte komunitní výukový program"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otevřít editor widgetů"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstranit widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Přidat widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedávno použila aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Právě používán aplikací <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedávno použila aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Podsvícení klávesnice"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Úroveň %1$d z %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index f6f40d0..9f5b600 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon for Bluetooth-enhed"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik for at konfigurere enhedsoplysninger"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik for at se alle enheder"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik for at parre en ny enhed"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriniveauet er ukendt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tilsluttet <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Forbundet til <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader hurtigt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Stryg mod venstre for at starte den fælles vejledning"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åbn redigeringsværktøjet til widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjern en widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tilføj widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Bruges af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Tastaturets baggrundslys"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d af %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index f8709c8..25d7202 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Symbol des Bluetooth-Geräts"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klicke, um das Gerätedetail zu konfigurieren"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klicken, um alle Geräte anzeigen zu lassen"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klicken, um neues Gerät zu koppeln"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akkustand unbekannt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbunden mit <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Wische nach links, um das gemeinsame Tutorial zu starten"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget-Editor öffnen"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Widget entfernen"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget hinzufügen"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kürzlich von <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Verwendet von <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kürzlich von <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Tastaturbeleuchtung"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d von %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index c42f04d..9bd246a 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Εικονίδιο συσκευής Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Κάντε κλικ για να διαμορφώσετε τις λεπτομέρειες συσκευής"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Κάντε κλικ για εμφάνιση όλων των συσκευών"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Κάντε κλικ για σύζευξη νέας συσκευής"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Άγνωστο ποσοστό μπαταρίας."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Συνδέθηκε σε <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Χρήση Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Συνδέθηκε"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Αποθηκεύτηκε"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Μπαταρία <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Σύρετε προς τα αριστερά για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Άνοιγμα προγράμ. επεξεργασίας γραφικών στοιχείων"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Αφαίρεση ενός widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Προσθήκη γραφικού στοιχείου"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Χρησιμοποιήθηκε πρόσφατα από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Χρησιμοποιείται από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Χρησιμοποιήθηκε πρόσφατα από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Οπίσθιος φωτισμός πληκτρολογίου"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Επίπεδο %1$d από %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index cc1b908..b3dea8d 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Keyboard backlight"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d of %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index c3c2d17..bbfdcea 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -397,8 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
-    <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
     <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index cc1b908..b3dea8d 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Keyboard backlight"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d of %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index cc1b908..b3dea8d 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Keyboard backlight"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d of %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1badbb3..5340b7b 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎Bluetooth connected.‎‏‎‎‏‎"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎Bluetooth device icon‎‏‎‎‏‎"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎Click to configure device detail‎‏‎‎‏‎"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎Click to see all devices‎‏‎‎‏‎"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎Click to pair new device‎‏‎‎‏‎"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎Battery percentage unknown.‎‏‎‎‏‎"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎Connected to ‎‏‎‎‏‏‎<xliff:g id="BLUETOOTH">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎Connected to ‎‏‎‎‏‏‎<xliff:g id="CAST">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎Use Bluetooth‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎Connected‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎Saved‎‏‎‎‏‎"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎disconnect‎‏‎‎‏‎"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎activate‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ battery‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‏‎Audio‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎Headset‎‏‎‎‏‎"</string>
@@ -397,8 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging slowly • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎Swipe left to start the communal tutorial‎‏‎‎‏‎"</string>
-    <string name="button_to_open_widget_picker" msgid="8007261659745030810">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‏‎‏‎‎Open the widget picker‎‏‎‎‏‎"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎Open the widget editor‎‏‎‎‏‎"</string>
     <string name="button_to_remove_widget" msgid="1511255853677835341">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎Remove a widget‎‏‎‎‏‎"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎Add Widget‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎Switch user‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎pulldown menu‎‏‎‎‏‎"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎All apps and data in this session will be deleted.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e0ad6ff..78f91b1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícono de dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Haz clic para configurar los detalles del dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Haz clic para ver todos los dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Haz clic para vincular un dispositivo nuevo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Se desconoce el porcentaje de la batería."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza el dedo a la izquierda para iniciar el instructivo comunal"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir el editor de widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Quita el widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Agregar widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Uso reciente en <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Uso reciente en <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación del teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 5685b31..59802ad 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icono de dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Haz clic para configurar la información del dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Haz clic para ver todos los dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Haz clic para emparejar un nuevo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentaje de batería desconocido."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza hacia la izquierda para iniciar el tutorial de la comunidad"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Eliminar un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Añadir widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
@@ -1074,7 +1076,7 @@
     <string name="person_available" msgid="2318599327472755472">"Disponible"</string>
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"No se ha podido leer el indicador de batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca la pantalla para consultar más información"</string>
-    <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna alarma puesta"</string>
+    <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna puesta"</string>
     <string name="accessibility_bouncer" msgid="5896923685673320070">"Poner bloqueo de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas digitales"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticarte"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación del teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 86f982b..ec65920 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-seadme ikoon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klõpsake seadme üksikasjade konfigureerimiseks"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kõigi seadmete kuvamiseks klõpsake"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Uue seadme sidumiseks klõpsake"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Aku laetuse protsent on teadmata."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Ühendatud ülekandega <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> akut"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kiirlaadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ühise õpetuse käivitamiseks pühkige vasakule"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidina redaktori avamine"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Eemalda vidin"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lisa vidin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Seda kasutab <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatuuri taustavalgustus"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Tase %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index fb89c04..b5a12cf 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth bidezko gailuaren ikonoa"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Gailuaren xehetasuna konfiguratzeko, sakatu hau"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Egin klik gailu guztiak ikusteko"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Egin klik beste gailu bat parekatzeko"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Hona konektatuta: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Bizkor kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Tutorial komuna hasteko, pasatu hatza ezkerrera"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ireki widget-editorea"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Kendu widget bat"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Gehitu widgeta"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) aplikazioak erabili du duela gutxi"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak darabil (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) aplikazioak erabili du duela gutxi"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Teklatuaren hondoko argia"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d/%2$d maila"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 7ab2ef1..055ea3b 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"نماد دستگاه بلوتوث"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"برای پیکربندی جزئیات دستگاه کلیک کنید"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"برای دیدن همه دستگاه‌ها، کلیک کنید"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"برای جفت کردن دستگاه جدید، کلیک کنید"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"درصد شارژ باتری مشخص نیست."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"به <xliff:g id="CAST">%s</xliff:g> متصل شد."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"استفاده از بلوتوث"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متصل"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ذخیره‌شده"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"برای شروع آموزش گام‌به‌گام عمومی، تند به‌چپ بکشید"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"باز کردن ویرایشگر ابزارک"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"حذف ابزارک"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"افزودن ابزارک"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایین‌پر"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامه‌ها و داده‌های این جلسه حذف خواهد شد."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده می‌کند (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"نور پس‌زمینه صفحه‌کلید"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"‏سطح %1$d از %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 9023d63..82e5231 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-laitekuvake"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Määritä laitteen asetukset klikkaamalla"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Katso kaikki laitteet klikkaamalla"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Muodosta uusi laitepari klikkaamalla"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akun varaustaso ei tiedossa."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Yhdistetty kohteeseen <xliff:g id="CAST">%s</xliff:g>"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu nopeasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aloita yhteisöesittely pyyhkäisemällä vasemmalle"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Avaa widgetien muokkaaja"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Poista widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lisää widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Tämän käytössä: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Näppämistön taustavalo"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Taso %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index f63f96f..619c7f8 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icône de l\'appareil Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cliquez pour configurer les détails de l\'appareil"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Cliquez ici pour voir tous les appareils"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Cliquez ici pour associer un nouvel appareil"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la pile inconnu."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer l\'écran vers la gauche pour démarrer le tutoriel communautaire"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Retirez le widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ajouter un widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Rétroéclairage du clavier"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3d765b2..9f22a57 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icône de l\'appareil Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cliquer pour configurer les détails de l\'appareil"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Cliquer pour afficher tous les appareils"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Cliquer pour associer un nouvel appareil"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la batterie inconnu."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batterie"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer vers la gauche pour démarrer le tutoriel collectif"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Retirez un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ajouter le widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En cours d\'utilisation par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Rétroéclairage du clavier"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d sur %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 5846c94..a7341b0 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona do dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Facer clic para configurar os detalles do dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Facer clic para ver todos os dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Facer clic para vincular un novo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Descoñécese a porcentaxe da batería."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Dispositivo conectado: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando rapidamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Pasa o dedo cara á esquerda para iniciar o titorial comunitario"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Quitar un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Engadir widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación do teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 7117702..2ca21c5 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"બ્લૂટૂથ ડિવાઇસનું આઇકન"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ડિવાઇસની વિગત ગોઠવવા માટે ક્લિક કરો"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"બધા ડિવાઇસ જોવા માટે ક્લિક કરો"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"નવું ડિવાઇસ જોડવા માટે ક્લિક કરો"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"બૅટરીની ટકાવારી અજાણ છે."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> થી કનેક્ટ થયાં."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> થી કનેક્ટ કરેલ."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"બ્લૂટૂથનો ઉપયોગ કરો"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"કનેક્ટેડ છે"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"સાચવેલું"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ડાબે સ્વાઇપ કરો"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"વિજેટ એડિટર ખોલો"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"કોઈ વિજેટ કાઢી નાખો"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"વિજેટ ઉમેરો"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
@@ -723,7 +726,7 @@
     <string name="switch_bar_off" msgid="5669805115416379556">"બંધ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ઉપલબ્ધ નથી"</string>
     <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"વધુ જાણો"</string>
-    <string name="nav_bar" msgid="4642708685386136807">"નેવિગેશન બાર"</string>
+    <string name="nav_bar" msgid="4642708685386136807">"નૅવિગેશન બાર"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"લેઆઉટ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"અતિરિક્ત ડાબો બટન પ્રકાર"</string>
     <string name="right_nav_bar_button_type" msgid="4472566498647364715">"અતિરિક્ત જમણો બટન પ્રકાર"</string>
@@ -742,7 +745,7 @@
     <string name="save" msgid="3392754183673848006">"સાચવો"</string>
     <string name="reset" msgid="8715144064608810383">"રીસેટ કરો"</string>
     <string name="clipboard" msgid="8517342737534284617">"ક્લિપબોર્ડ"</string>
-    <string name="accessibility_key" msgid="3471162841552818281">"કસ્ટમ નેવિગેશન બટન"</string>
+    <string name="accessibility_key" msgid="3471162841552818281">"કસ્ટમ નૅવિગેશન બટન"</string>
     <string name="left_keycode" msgid="8211040899126637342">"ડાબો કીકોડ"</string>
     <string name="right_keycode" msgid="2480715509844798438">"જમણો કીકોડ"</string>
     <string name="left_icon" msgid="5036278531966897006">"ડાબું આઇકન"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) દ્વારા તાજેતરમાં ઉપયોગ કરવામાં આવ્યો"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) દ્વારા ઉપયોગ ચાલુ છે"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) દ્વારા તાજેતરમાં ઉપયોગ કરવામાં આવ્યો"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"કીબોર્ડની બૅકલાઇટ"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dમાંથી %1$d લેવલ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fa44aed..6a7d143 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लूटूथ डिवाइस का आइकॉन"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिवाइस की जानकारी कॉन्फ़िगर करने के लिए क्लिक करें"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"\'सभी डिवाइस देखें\' पर क्लिक करें"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"\'नया डिवाइस जोड़ें\' पर क्लिक करें"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"इस बारे में जानकारी नहीं है कि अभी बैटरी कितने प्रतिशत चार्ज है."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> से कनेक्ट है."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ इस्तेमाल करें"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट है"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बैटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • तेज़ चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, बाईं ओर स्वाइप करें"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोलें"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"विजेट को हटाएं"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट जोड़ें"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"हाल ही में, <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ने इस्तेमाल किया"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) पर इस्तेमाल किया जा रहा है"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"हाल ही में, <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ने इस्तेमाल किया"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"कीबोर्ड की बैकलाइट"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d में से %1$d लेवल"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index c3a4ec2..4469d78 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da biste konfigurirali pojedinosti o uređaju"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite za prikaz svih uređaja"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite da biste uparili novi uređaj"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak baterije nije poznat."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani ste sa sljedećim uređajem: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uključi"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • brzo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prijeđite prstom ulijevo da biste pokrenuli zajednički vodič"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje alata za uređivanje widgeta"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Uklanjanje widgeta"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koristi: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Pozadinsko osvjetljenje tipkovnice"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Razina %1$d od %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5552b51..51c3932 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-eszköz ikon"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kattintson az eszköz beállításainak megadásához"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kattintson az összes eszköz megtekintéséhez"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kattintson új eszköz párosításához"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Az akkumulátor töltöttségi szintje ismeretlen."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Csatlakozva a következőhöz: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Gyors töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Csúsztasson gyorsan balra a közösségi útmutató elindításához"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"A modulszerkesztő megnyitása"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"A modul eltávolítása"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Modul hozzáadása"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Legutóbb használta: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Használatban a következő által: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Legutóbb használta: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"A billentyűzet háttérvilágítása"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Fényerő: %2$d/%1$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 714d500..917fb77 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth սարքի պատկերակ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Սեղմեք՝ սարքի մանրամասները կազմաձևելու համար"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Սեղմեք՝ բոլոր սարքերը տեսնելու համար"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Սեղմեք՝ նոր սարք զուգակցելու համար"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Մարտկոցի լիցքի մակարդակն անհայտ է։"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Միացված է <xliff:g id="CAST">%s</xliff:g>-ին:"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Միացնել Bluetooth-ը"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Միացված է"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Պահված է"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Արագ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Թերթեք ձախ՝ ուղեցույցը գործարկելու համար"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Բացել վիջեթների խմբագրիչը"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Հեռացնել վիջեթը"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ավելացնել վիջեթ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Օգտագործվում է <xliff:g id="APP_NAME">%1$s</xliff:g>ի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Հետին լուսավորությամբ ստեղնաշար"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d՝ %2$d-ից"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 1ff866e..83db83b 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon perangkat Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik untuk mengonfigurasi detail perangkat"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik untuk melihat semua perangkat"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik untuk menyambungkan perangkat baru"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Persentase baterai tidak diketahui."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Terhubung ke <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan koneksi"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan cepat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Geser ke kiri untuk memulai tutorial komunal"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Hapus widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tambahkan Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Sedang digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Lampu latar keyboard"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Tingkat %1$d dari %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 82e470f..bf0b73d 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Tákn Bluetooth-tækis"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Smelltu til að stilla tækjaupplýsingar"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Smelltu til að sjá öll tæki"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Smelltu til að para nýtt tæki"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Staða rafhlöðu óþekkt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Tengt við <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hraðhleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Strjúktu til vinstri til að hefja samfélagsleiðsögnina"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Opna græjuritilinn"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjarlægja græju"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Bæta græju við"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Í notkun í <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Baklýsing lyklaborðs"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Stig %1$d af %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 50fc1ee..e65e3aa 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona del dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Fai clic per configurare i dettagli del dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Fai clic per vedere tutti i dispositivi"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Fai clic per accoppiare un nuovo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentuale della batteria sconosciuta."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Connesso a: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica veloce • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Scorri a sinistra per iniziare il tutorial della community"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Apri l\'editor del widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Rimuovi un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Aggiungi widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroilluminazione della tastiera"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Livello %1$d di %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 718cfb4..a14aa1b 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‏Bluetooth מחובר."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"‏סמל של מכשיר Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"יש ללחוץ כדי להגדיר את פרטי המכשיר"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"אפשר ללחוץ כדי לראות את כל המכשירים"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"צריך ללחוץ כדי להתאים מכשיר חדש"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"מחובר אל <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> סוללה"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"אפשר להחליק שמאלה כדי להפעיל את המדריך המשותף"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"פתיחה של הכלי לעריכת ווידג\'טים"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"הסרה של ווידג\'ט"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"הוספת ווידג\'ט"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"בשימוש על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"התאורה האחורית במקלדת"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"‏רמה %1$d מתוך %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index a699f93..75166d9 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth デバイスのアイコン"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"クリックしてデバイスの詳細を設定します"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"クリックすると、すべてのデバイスが表示されます"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"クリックすると、新しいデバイスをペア設定できます"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"バッテリー残量は不明です。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>に接続されています。"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"バッテリー <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"左にスワイプすると、コミュニティ チュートリアルが開始します"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ウィジェット エディタを開く"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ウィジェットを削除します"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ウィジェットを追加"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> が最近使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> が使用中(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> が最近使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"キーボード バックライト"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"レベル %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index cf3a740..b363f99 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth მოწყობილობის ხატულა"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"დააწკაპუნეთ მოწყობილობის დეტალების კონფიგურირებისთვის"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"დააწკაპუნეთ ყველა მოწყობილობის სანახავად"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"დააწკაპუნეთ ახალი მოწყობილობის დასაწყვილებლად"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ბატარეის პროცენტული მაჩვენებელი უცნობია."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"დაკავშირებულია მოწყობილობასთან: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ის გამოყენება"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"დაკავშირებული"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"შენახული"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ბატარეა"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • სწრაფად იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"გადაფურცლეთ მარცხნივ, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"გახსენით ვიჯეტის რედაქტორი"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ამოშალეთ ვიჯეტი"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ვიჯეტის დამატება"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"გამოიყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"კლავიატურის შენათება"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"დონე: %1$d %2$d-დან"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d8eaba0..b887e4b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth құрылғысы белгішесі"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Құрылғы деректерін конфигурациялау үшін басыңыз."</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Барлық құрылғыны көру үшін басыңыз."</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Жаңа құрылғы жұптау үшін басыңыз."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> трансляциясына қосылды."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сақталды"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батарея деңгейі: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жылдам зарядтау • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ортақ оқулықты ашу үшін солға қарай сырғытыңыз."</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет редакторын ашу"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетті өшіру"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет қосу"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) қолданбасы пайдаланды."</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланып жатыр"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланды."</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Пернетақта жарығы"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Деңгей: %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 6c93685..0a4c2d8 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បាន​តភ្ជាប់​ប៊្លូធូស។"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"រូបឧបករណ៍​ប៊្លូធូស"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ចុចដើម្បីកំណត់រចនាសម្ព័ន្ធព័ត៌មានលម្អិតអំពីឧបករណ៍"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ចុច ដើម្បីមើលឃើញឧបករណ៍ទាំងអស់"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ចុច ដើម្បីផ្គូផ្គងឧបករណ៍ថ្មី"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"មិនដឹងអំពី​ភាគរយថ្មទេ។"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បាន​ភ្ជាប់​ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"បានភ្ជាប់ទៅ <xliff:g id="CAST">%s</xliff:g>"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ប្រើប៊្លូធូស"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"បានភ្ជាប់"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"បាន​រក្សាទុក"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុង​សាកថ្ម​យឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"អូសទៅឆ្វេង ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"បើកកម្មវិធីកែធាតុ​ក្រាហ្វិក"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ដកធាតុក្រាហ្វិកចេញ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"បញ្ចូលធាតុក្រាហ្វិក"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរ​អ្នក​ប្រើ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយ​ទាញចុះ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យ​ទាំងអស់​ក្នុង​វគ្គ​នេះ​នឹង​ត្រូវ​លុប។"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"បានប្រើនាពេលថ្មីៗនេះដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"កំពុងប្រើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"បានប្រើនាពេលថ្មីៗនេះដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ពន្លឺក្រោយក្ដារចុច"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"កម្រិតទី %1$d នៃ %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index ff2d4c5..850bb0a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್‌‌ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ಬ್ಲೂಟೂತ್ ಸಾಧನ ಐಕಾನ್"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ಸಾಧನದ ವಿವರಗಳನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ಎಲ್ಲಾ ಸಾಧನಗಳನ್ನು ನೋಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ಬ್ಯಾಟರಿ ಶೇಕಡಾವಾರು ತಿಳಿದಿಲ್ಲ."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ಬ್ಲೂಟೂತ್ ಬಳಸಿ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್‌ಸೆಟ್"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ಸಮುದಾಯದ ಟ್ಯುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ವಿಜೆಟ್ ಎಡಿಟರ್ ಅನ್ನು ತೆರೆಯಿರಿ"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ವಿಜೆಟ್ ತೆಗೆದುಹಾಕಿ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ವಿಜೆಟ್ ಸೇರಿಸಿ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್‌ಡೌನ್ ಮೆನು"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್‌ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
@@ -509,7 +511,7 @@
     <string name="sound_settings" msgid="8874581353127418308">"ಧ್ವನಿ &amp; ವೈಬ್ರೇಷನ್"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"ವಾಲ್ಯೂಮ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮಟ್ಟಕ್ಕೆ ತಗ್ಗಿಸಲಾಗಿದೆ"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"ಹೆಡ್‌ಫೋನ್‌ನ ವಾಲ್ಯೂಮ್‌ ಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ಹೆಚ್ಚಿನ ಸಮಯದವರೆಗೆ ಅಧಿಕವಾಗಿದೆ"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"ಶಿಫಾರಸು ಮಾಡಿದ್ದಕ್ಕಿಂತಲೂ ದೀರ್ಘಕಾಲ ಹೆಡ್‌ಫೋನ್‌ನ ವಾಲ್ಯೂಮ್‌ ಹೆಚ್ಚಿಗೆ ಇದೆ"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"ಹೆಡ್‌ಫೋನ್‌ನ ವಾಲ್ಯೂಮ್ ಈ ವಾರದ‌ ಮಟ್ಟಿಗೆ ಸುರಕ್ಷಿತ ಮಿತಿಯನ್ನು ಮೀರಿದೆ"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"ಆಲಿಸುತ್ತಿರಿ"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"ವಾಲ್ಯೂಮ್ ತಗ್ಗಿಸಿ"</string>
@@ -740,7 +742,7 @@
     <item msgid="7453955063378349599">"ಎಡ-ಬಾಗುವಿಕೆ"</item>
     <item msgid="5874146774389433072">"ಬಲ-ಬಾಗುವಿಕೆ"</item>
   </string-array>
-    <string name="save" msgid="3392754183673848006">"ಉಳಿಸಿ"</string>
+    <string name="save" msgid="3392754183673848006">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="reset" msgid="8715144064608810383">"ಮರುಹೊಂದಿಸಿ"</string>
     <string name="clipboard" msgid="8517342737534284617">"ಕ್ಲಿಪ್‌ಬೋರ್ಡ್"</string>
     <string name="accessibility_key" msgid="3471162841552818281">"ಕಸ್ಟಮ್ ನ್ಯಾವಿಗೇಷನ್ ಬಟನ್"</string>
@@ -1027,7 +1029,7 @@
     <string name="media_output_broadcasting_message" msgid="4150299923404886073">"ನಿಮ್ಮ ಪ್ರಸಾರವನ್ನು ಆಲಿಸಲು, ಹೊಂದಾಣಿಕೆಯಾಗುವ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಹೊಂದಿರುವ ಸಮೀಪದಲ್ಲಿರುವ ಜನರು ನಿಮ್ಮ QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಬಹುದು ಅಥವಾ ನಿಮ್ಮ ಪ್ರಸಾರದ ಹೆಸರು ಹಾಗೂ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಬಳಸಬಹುದು"</string>
     <string name="media_output_broadcast_name" msgid="8786127091542624618">"ಪ್ರಸಾರದ ಹೆಸರು"</string>
     <string name="media_output_broadcast_code" msgid="870795639644728542">"ಪಾಸ್‌ವರ್ಡ್"</string>
-    <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"ಉಳಿಸಿ"</string>
+    <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="media_output_broadcast_starting" msgid="8130153654166235557">"ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="media_output_broadcast_start_failed" msgid="3670835946856129775">"ಪ್ರಸಾರ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ಬಳಕೆಯಲ್ಲಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ಕೀಬೋರ್ಡ್ ಬ್ಯಾಕ್‌ಲೈಟ್"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d ರಲ್ಲಿ %1$d ಮಟ್ಟ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index ddab174..a9395b2 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"블루투스 기기 아이콘"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"기기 세부정보를 구성하려면 클릭하세요."</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"모든 기기를 보려면 클릭하세요"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"새 기기를 페어링하려면 클릭하세요"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"배터리 잔량을 알 수 없습니다."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>에 연결됨"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"블루투스 사용"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"연결됨"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"저장됨"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"배터리 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 고속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"공동 튜토리얼을 시작하려면 왼쪽으로 스와이프하세요"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"위젯 편집기 열기"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"위젯 삭제"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"위젯 추가"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용 중(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"키보드 백라이트"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d단계 중 %1$d단계"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 90ba042..73e7647 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth түзмөгүнүн сүрөтчөсү"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Түзмөктүн чоо-жайын конфигурациялоо үчүн чыкылдатыңыз"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Бардык түзмөктөрдү көрүү үчүн чыкылдатыңыз"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Жаңы түзмөктү жупташтыруу үчүн чыкылдатыңыз"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея кубатынын деңгээли белгисиз."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> менен туташты."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Иштетүү"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Туташты"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сакталды"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батареянын деңгээли <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Тез кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Жалпы үйрөткүчтү иштетүү үчүн солго сүрүңүз"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет түзөткүчтү ачуу"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетти өчүрүү"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет кошуу"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) колдонмосунда иштетилди"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилип жатат (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) колдонмосунда иштетилди"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Баскычтоптун жарыгы"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d ичинен %1$d-деңгээл"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index bdd0f2e8..e9f6a19 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ໄອຄອນອຸປະກອນ Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ຄລິກເພື່ອຕັ້ງຄ່າລາຍລະອຽດອຸປະກອນ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ຄລິກເພື່ອເບິ່ງອຸປະກອນທັງໝົດ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ຄລິກເພື່ອຈັບຄູ່ອຸປະກອນໃໝ່"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ບໍ່ຮູ້ເປີເຊັນແບັດເຕີຣີ."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມ​ຕໍ່​ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="CAST">%s</xliff:g> ແລ້ວ."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ໃຊ້ Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ບັນທຶກແລ້ວ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ແບັດເຕີຣີ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ປັດຊ້າຍເພື່ອເລີ່ມບົດແນະນຳສ່ວນກາງ"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ເປີດຕົວແກ້ໄຂວິດເຈັດ"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ລຶບວິດເຈັດອອກ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ເພີ່ມວິດເຈັດ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯ​ແລະ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ໃນ​ເຊດ​ຊັນ​ນີ້​ຈະ​ຖືກ​ລຶບ​ອອກ."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"ໃຊ້ຢູ່ໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ໄຟປຸ່ມແປ້ນພິມ"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"ລະດັບທີ %1$d ຈາກ %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index d86eeb9..0e9e940 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"„Bluetooth“ įrenginio piktograma"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Spustelėkite, jei norite konfigūruoti išsamią įrenginio informaciją"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Spustelėkite, kad peržiūrėtumėte visus įrenginius"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Spustelėkite, kad susietumėte naują įrenginį"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumuliatoriaus energija procentais nežinoma."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Prisijungta prie <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sparčiai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Perbraukite kairėn, paleistumėte bendruomenės mokomąją medžiagą"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atidaryti valdiklio redagavimo programą"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Pašalinti valdiklį"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pridėti valdiklį"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Naudoja <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatūros foninis apšvietimas"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d lygis iš %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a98cba0..8c7af63 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth ierīces ikona"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Lai konfigurētu ierīces informāciju, noklikšķiniet"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Noklikšķiniet, lai skatītu visas ierīces"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumulatora uzlādes līmenis procentos nav zināms."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Savienots ar ierīci <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumulators: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ātrā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Velciet pa kreisi, lai palaistu kopienas pamācību."</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atvērt logrīku redaktoru"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Noņemt logrīku"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pievienot logrīku"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"To izmanto lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Tastatūras fona apgaismojums"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Līmenis numur %1$d, kopā ir %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 0464bb3..6c577ce 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона за уред со Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликнете за да ги конфигурирате деталите за уредот"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликнете за да се прикажат сите уреди"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликнете за да спарите нов уред"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентот на батеријата е непознат."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Поврзано со <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Поврзано"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Зачувано"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батерија"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -397,9 +401,10 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Повлечете налево за да го започнете заедничкото упатство"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
+    <!-- no translation found for button_to_open_widget_editor (5599945944349057600) -->
     <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Отстранува виџет"</string>
+    <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
@@ -1209,8 +1214,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Се користи од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Осветлување на тастатура"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ниво %1$d од %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index b4ff314..7354e91 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്‌റ്റുചെയ്തു."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth ഉപകരണ ഐക്കൺ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ഉപകരണത്തിന്റെ വിശദാംശങ്ങൾ കോൺഫിഗർ ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"എല്ലാ ഉപകരണങ്ങളും കാണാൻ ക്ലിക്ക് ചെയ്യുക"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"പുതിയ ഉപകരണം ജോടിയാക്കാൻ ക്ലിക്ക് ചെയ്യുക"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ബാറ്ററി ശതമാനം അജ്ഞാതമാണ്."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്‌റ്റുചെയ്‌തു."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ഉപയോഗിക്കുക"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"സംരക്ഷിച്ചു"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ബാറ്ററി"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്‌സെറ്റ്"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ ഇടത്തോട്ട് സ്വൈപ്പ് ചെയ്യുക"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"വിജറ്റ് എഡിറ്റർ തുറക്കുക"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"വിജറ്റ് നീക്കം ചെയ്യുക"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"വിജറ്റ് ചേർക്കുക"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) അടുത്തിടെ ഉപയോഗിച്ചു"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ഉപയോഗിക്കുന്നു"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) അടുത്തിടെ ഉപയോഗിച്ചു"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"കീബോഡ് ബാക്ക്‌ലൈറ്റ്"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d-ൽ %1$d-ാമത്തെ ലെവൽ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 42b0cae..e6dc9e5 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth төхөөрөмжийн дүрс тэмдэг"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Төхөөрөмжийн дэлгэрэнгүйг тохируулахын тулд товшино уу"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Бүх төхөөрөмжийг харахын тулд товшино уу"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Шинэ төхөөрөмж хослуулахын тулд товшино уу"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарейн хувь тодорхойгүй байна."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>-д холбогдсон."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-г ашиглах"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Холбогдсон"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Хадгалсан"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Хурдтай цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Нийтийн практик хичээлийг эхлүүлэхийн тулд зүүн тийш шударна уу"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет засварлагчийг нээх"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетийг хасах"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет нэмэх"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ашигласан"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашиглаж байна"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашигласан"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Гарын арын гэрэл"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d-с %1$d-р түвшин"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index ffa36a4..a5d0afa 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्‍ट केले."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लूटूथ डिव्‍हाइस आयकन"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिव्हाइसचे तपशील कॉंफिगर करण्यासाठी क्लिक करा"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"सर्व डिव्हाइस पाहण्यासाठी क्लिक करा"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"नवीन डिव्हाइस पेअर करण्यासाठी क्लिक करा"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"बॅटरीच्या चार्जिंगची टक्केवारी माहित नाही."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्‍ट केले."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> शी कनेक्ट केले."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्‍लूटूथ वापरा"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट केले"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव्ह केले"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी डावीकडे स्वाइप करा"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट संपादक उघडा"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"विजेट काढून टाका"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट जोडा"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अ‍ॅप्स आणि डेटा हटवला जाईल."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"अलीकडे <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ने वापरले"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) द्वारे वापरले जात आहे"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"अलीकडे <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ने वापरले"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"कीबोर्ड बॅकलाइट"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d पैकी %1$d पातळी"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 7e0bba4..bf0a60c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon peranti Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik untuk mengkonfigurasi butiran peranti"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik untuk melihat semua peranti"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik untuk menggandingkan peranti baharu"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Peratusan kuasa bateri tidak diketahui."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Disambungkan ke <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Leret ke kiri untuk memulakan tutorial umum"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Alih keluar widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tambahkan Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Digunakan baru-baru ini oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Digunakan baru-baru ini oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Cahaya latar papan kekunci"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Tahap %1$d daripada %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 9b3f6ca..5568fa7 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ဘလူးတုသ်သုံးစက် သင်္ကေတ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"စက်အသေးစိတ်ကို စီစဉ်သတ်မှတ်ရန် နှိပ်ပါ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"စက်အားလုံးကြည့်ရန် နှိပ်ပါ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"စက်အသစ် တွဲချိတ်ရန် နှိပ်ပါ"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ဘက်ထရီရာခိုင်နှုန်းကို မသိပါ။"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> သို့ချိတ်ဆက်ထားပါသည်။"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ဘလူးတုသ်သုံးရန်"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ချိတ်ဆက်ထားသည်"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
@@ -396,11 +400,11 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အမြန်အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် ဘယ်သို့ပွတ်ဆွဲပါ"</string>
+    <!-- no translation found for button_to_open_widget_editor (5599945944349057600) -->
     <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ဝိဂျက် ဖယ်ရှားရန်"</string>
+    <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
@@ -1210,8 +1214,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က သုံးနေသည်"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ကီးဘုတ်နောက်မီး"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"အဆင့် %2$d အနက် %1$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 0f73d98..c654397 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon for Bluetooth-enheter"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klikk for å konfigurere enhetsdetaljer"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klikk for å se alle enhetene"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klikk for å koble sammen en ny enhet"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriprosenten er ukjent."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Koblet til <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader raskt • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Sveip til venstre for å starte fellesveiledningen"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åpne redigeringsverktøyet for moduler"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjern en modul"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Legg til en modul"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nylig brukt av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"I bruk av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nylig brukt av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Bakgrunnslys for tastatur"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivå %1$d av %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index b921f58..71fa4e5 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लुटुथ डिभाइस जनाउने आइकन"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिभाइसको विवरण कन्फिगर गर्न क्लिक गर्नुहोस्"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"सबै डिभाइसहरू हेर्न क्लिक गर्नुहोस्"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"नयाँ डिभाइसमा कनेक्ट गर्न क्लिक गर्नुहोस्"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ब्याट्रीमा कति प्रतिशत चार्ज छ भन्ने कुराको जानाकरी छैन।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> मा कनेक्ट गरियो।"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लुटुथ प्रयोग गर्नुहोस्"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट गरिएको छ"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेभ गरिएको छ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ब्याट्री"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्युनल ट्युटोरियल सुरु गर्न बायाँतिर स्वाइप गर्नुहोस्"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोल्नुहोस्"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"कुनै विजेट हटाउनुहोस्"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट हाल्नुहोस्"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ले हालसालै प्रयोग गरेको"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ले प्रयोग गरिरहेको छ"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ले हालसालै प्रयोग गरेको"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"किबोर्ड ब्याकलाइट"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d मध्ये %1$d औँ स्तर"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 9e07015..c8f6c58 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icoon voor bluetooth-apparaat"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik om de apparaatgegevens in te stellen"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik om alle apparaten te bekijken"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik om nieuw apparaat te koppelen"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterijpercentage onbekend."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbonden met <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterijniveau"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe naar links om de communitytutorial te starten"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"De widget-editor openen"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Verwijder een widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget toevoegen"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recent gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recent gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Achtergrondverlichting van toetsenbord"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d van %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 62bfdec..c62519da 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍‍‌ ସଂଯୋଗ କରାଯାଇଛି।"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଆଇକନ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ଡିଭାଇସ ବିବରଣୀକୁ କନଫିଗର କରିବା ପାଇଁ କ୍ଲିକ କରନ୍ତୁ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ସମସ୍ତ ଡିଭାଇସ ଦେଖିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ନୂଆ ଡିଭାଇସ ପେୟାର କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ବ୍ୟାଟେରୀ ଶତକଡ଼ା ଅଜଣା ଅଟେ।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ସହିତ ସଂଯୁକ୍ତ।"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ସେଭ କରାଯାଇଛି"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ବ୍ୟାଟେରୀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍‍"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ବାମକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ୱିଜେଟ ଏଡିଟର ଖୋଲନ୍ତୁ"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ଏକ ୱିଜେଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍‍ ବଦଳାନ୍ତୁ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍‌ ଓ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ।"</string>
@@ -413,7 +415,7 @@
     <string name="guest_notification_session_active" msgid="5567273684713471450">"ଆପଣ ଅତିଥି ମୋଡରେ ଅଛନ୍ତି"</string>
     <string name="user_add_user_message_guest_remove" msgid="5589286604543355007">\n\n"ଜଣେ ନୂଆ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଗ କରିବା ଦ୍ୱାରା ଅତିଥି ମୋଡରୁ ବାହାରି ଯିବ ଏବଂ ବର୍ତ୍ତମାନର ଅତିଥି ସେସନରୁ ସମସ୍ତ ଆପ ଓ ଡାଟା ଡିଲିଟ ହୋଇଯିବ।"</string>
     <string name="user_limit_reached_title" msgid="2429229448830346057">"ଉପଯୋଗକର୍ତ୍ତା ସୀମାରେ ପହଞ୍ଚିଛି"</string>
-    <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{କେବଳ ଜଣେ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଇପାରିବ।}other{କେବଳ # ଜଣ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଇପାରିବ।}}"</string>
+    <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{କେବଳ ଜଣେ ୟୁଜର ତିଆରି କରାଯାଇପାରିବ।}other{କେବଳ # ଜଣ ୟୁଜର ତିଆରି କରାଯାଇପାରିବ।}}"</string>
     <string name="user_remove_user_title" msgid="9124124694835811874">"ୟୁଜରଙ୍କୁ ବାହାର କରିବେ?"</string>
     <string name="user_remove_user_message" msgid="6702834122128031833">"ଏହି ୟୁଜରଙ୍କ ସମସ୍ତ ଆପ୍‍ ଓ ଡାଟା ଡିଲିଟ୍‍ ହେବ।"</string>
     <string name="user_remove_user_remove" msgid="8387386066949061256">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
@@ -509,7 +511,7 @@
     <string name="sound_settings" msgid="8874581353127418308">"ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେସନ"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"ସେଟିଂସ"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"ଭଲ୍ୟୁମକୁ ସୁରକ୍ଷିତ ଲେଭେଲକୁ କମ କରାଯାଇଛି"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"ସୁପାରିଶ କରାଯାଇଥିବା ଅପେକ୍ଷା ଅଧିକ ସମୟ ପାଇଁ ହେଡଫୋନର ଭଲ୍ୟୁମ ଅଧିକ ଅଛି"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"ସୁପାରିଶ ଭଲ୍ୟୁମ ଠାରୁ ହେଡଫୋନର ଭଲ୍ୟୁମ ଅଧିକ ଅଛି"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"ଏହି ସପ୍ତାହ ପାଇଁ ହେଡଫୋନର ଭଲ୍ୟୁମ ସୁରକ୍ଷିତ ସୀମାକୁ ଅତିକ୍ରମ କରିଛି"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"ଶୁଣିବା ଜାରି ରଖନ୍ତୁ"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"ଭଲ୍ୟୁମ କମାନ୍ତୁ"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ଏବେ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ଏବେ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"କୀବୋର୍ଡ ବେକଲାଇଟ"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dରୁ %1$d ନମ୍ବର ଲେଭେଲ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index f14962e..3f75dfe 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਤੀਕ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ਡੀਵਾਈਸ ਦੇ ਵੇਰਵੇ ਦਾ ਸੰਰੂਪਣ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"\'ਸਾਰੇ ਡੀਵਾਈਸ ਦੇਖੋ\' \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"\'ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ\' \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ਬੈਟਰੀ ਪ੍ਰਤੀਸ਼ਤ ਅਗਿਆਤ ਹੈ।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ਬਲੂਟੁੱਥ ਵਰਤੋ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ਕਨੈਕਟ ਹੈ"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
@@ -363,7 +367,7 @@
     <string name="zen_alarms_introduction" msgid="3987266042682300470">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string>
     <string name="zen_priority_customize_button" msgid="4119213187257195047">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
     <string name="zen_silence_introduction_voice" msgid="853573681302712348">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓ, ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਨੂੰ ਬਲਾਕ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਫ਼ੋਨ ਕਾਲ ਕਰਨ ਦੇ ਯੋਗ ਹੋਵੋਂਗੇ।"</string>
-    <string name="zen_silence_introduction" msgid="6117517737057344014">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓਜ਼, ਅਤੇ ਗੇਮਸ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਵਾਇਬ੍ਰੇਸ਼ਨ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ।"</string>
+    <string name="zen_silence_introduction" msgid="6117517737057344014">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ।"</string>
     <string name="notification_tap_again" msgid="4477318164947497249">"ਖੋਲ੍ਹਣ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string>
     <string name="tap_again" msgid="1315420114387908655">"ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ਖੋਲ੍ਹਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ਵਿਜੇਟ ਸੰਪਾਦਕ ਖੋਲ੍ਹੋ"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ਵਿਜੇਟ ਨੂੰ ਹਟਾਓ"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ਹਾਲ ਹੀ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ਵੱਲੋਂ ਵਰਤਿਆ ਗਿਆ"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ਵੱਲੋਂ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ਹਾਲ ਹੀ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ਵੱਲੋਂ ਵਰਤਿਆ ਗਿਆ"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ਕੀ-ਬੋਰਡ ਬੈਕਲਾਈਟ"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d ਵਿੱਚੋਂ %1$d ਪੱਧਰ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 4be47d4..a26a5b5 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona urządzenia Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknij, aby skonfigurować szczegóły urządzenia"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknij, aby zobaczyć wszystkie urządzenia"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknij, aby sparować nowe urządzenie"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Poziom naładowania baterii jest nieznany."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Połączono z urządzeniem <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Użyj Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Szybkie ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aby uruchomić wspólny samouczek, przeciągnij palcem w lewo"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otwórz edytor widżetów"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Usuń widżet"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj widżet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Podświetlenie klawiatury"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Poziom %1$d z %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 509a3bf..4b56060 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar os detalhes do dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para conferir todos os dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para parear o novo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover um widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz de fundo do teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 46476c0..09c48cb 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar o detalhe do dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para ver todos os dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para sincronizar um novo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Ligado a <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize rapidamente para a esquerda para iniciar o tutorial coletivo"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em utilização pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz do teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 509a3bf..4b56060 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar os detalhes do dispositivo"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para conferir todos os dispositivos"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para parear o novo dispositivo"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover um widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz de fundo do teclado"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index c737b5d..aaf20f3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Pictograma de dispozitiv Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Dă clic pentru a configura detaliile dispozitivului"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Dă clic pentru a vedea toate dispozitivele"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Dă clic pentru a asocia noul dispozitiv"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procentajul bateriei este necunoscut."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"S-a stabilit conexiunea la <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Glisează spre stânga pentru a începe tutorialul pentru comunitate"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Deschide editorul de widgeturi"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Elimină un widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adaugă widgetul"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Se folosește pentru <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Iluminarea din spate a tastaturii"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivelul %1$d din %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index bfd0056..3836a29 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок устройства Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Нажмите, чтобы изменить информацию об устройстве"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Нажмите, чтобы показать все устройства"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Нажмите, чтобы подключить устройство"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Уровень заряда батареи в процентах неизвестен."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Подключено к: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Подключено"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сохранено"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Быстрая зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Чтобы ознакомиться с руководством, проведите по экрану влево"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Открыть редактор виджетов"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Удалить виджет"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Добавить виджет"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Сейчас используется приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Подсветка клавиатуры"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Уровень %1$d из %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index eb3b8e5..409379c 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"බ්ලූටූත් උපාංග නිරූපකය"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"උපාංග විස්තර වින්‍යාස කිරීමට ක්ලික් කරන්න"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"සියලු උපාංග බැලීමට ක්ලික් කරන්න"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"නව උපාංගය යුගල කිරීමට ක්ලික් කරන්න"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"බැටරි ප්‍රතිශතය නොදනී."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> වෙත සම්බන්ධ විය."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"බ්ලූටූත් භාවිතා කරන්න"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"සම්බන්ධිතයි"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"සුරැකිණි"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්‍රිය කරන්න"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්‍රව්‍ය"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"පොදු නිබන්ධනය ආරම්භ කිරීමට වමට ස්වයිප් කරන්න"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"විජට් සංස්කාරකය විවෘත කරන්න"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"විජට්ටුවක් ඉවත් කරන්න"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"විජට්ටුව එක් කරන්න"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> භාවිත කරයි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"යතුරු පුවරු පසු ආලෝකය"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dන් %1$d වැනි මට්ටම"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 6c4505b..b4bf8d3 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona zariadenia s rozhraním Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknutím nakonfigurujte podrobnosti o zariadení"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknutím zobrazíte všetky zariadenia"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknutím spárujete nové zariadenie"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percento batérie nie je známe."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Pripojené k zariadeniu <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa rýchlo • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Potiahnutím doľava spustite komunitný návod"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvoriť editor miniaplikácií"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstrániť miniaplikáciu"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pridať miniaplikáciu"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Využíva <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Podsvietenie klávesnice"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d. úroveň z %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index eddb50c..8c4bd73 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona naprave Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite za konfiguriranje podrobnosti o napravi"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite za ogled vseh naprav"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite za seznanitev nove naprave"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Neznan odstotek napolnjenosti baterije."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Vzpostavljena povezava: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hitro polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Povlecite levo, da zaženete vadnico za skupnost"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Odpiranje urejevalnika pripomočkov"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstranitev pripomočka"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj pripomoček"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Uporablja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Osvetlitev tipkovnice"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Stopnja %1$d od %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 9848298..5a1cd59 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona e pajisjes me Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliko për të konfiguruar detajet e pajisjes"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliko për të shikuar të gjitha pajisjet"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliko për të çiftuar një pajisje të re"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Përqindja e baterisë e panjohur."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Është lidhur me <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string>
@@ -396,11 +400,11 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet shpejt • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Rrëshqit shpejt majtas për të filluar udhëzuesin e përbashkët"</string>
+    <!-- no translation found for button_to_open_widget_editor (5599945944349057600) -->
     <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Hiq një miniaplikacion"</string>
+    <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
@@ -1210,8 +1214,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Në përdorim nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Drita e sfondit e tastierës"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveli: %1$d nga %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 8711abd..887f5c6 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона Bluetooth уређаја"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликните да бисте конфигурисали детаље о уређају"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликните да бисте видели све уређаје"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликните да бисте упарили нов уређај"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Проценат напуњености батерије није познат."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Повезани смо са уређајем <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Повезано"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сачувано"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Брзо се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Превуците улево да бисте започели заједнички водич"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отвори уређивач виџета"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Уклони виџет"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Додај виџет"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Недавно користила апликација <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Користе <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Недавно користила апликација <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Позадинско осветљење тастатуре"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d. ниво од %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 9568964..3f9e45c 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Enhetsikon för Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klicka för att konfigurera enhetsinformation"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klicka för att se alla enheter"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klicka för att parkoppla en ny enhet"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Okänd batterinivå."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Ansluten till <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas snabbt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Svep åt vänster för att börja med gruppguiden"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Öppna widgetredigeraren"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Ta bort en widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lägg till widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Används av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Bakgrundsbelysning för tangentbord"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivå %1$d av %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 7535d68..007f0d3 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Aikoni ya Kifaa chenye Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Bofya ili uweke mipangilio ya maelezo ya kifaa"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Bofya ili uone vifaa vyote"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Bofya ili uoanishe kifaa kipya"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Asilimia ya betri haijulikani."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Imeunganishwa kwenye <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji kwa kasi • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Telezesha kidole kushoto ili uanze mafunzo ya pamoja"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Fungua kihariri cha wijeti"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Ondoa wijeti"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Weka Wijeti"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Inatumiwa na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Mwanga chini ya kibodi"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Kiwango cha %1$d kati ya %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 8327190..e5cf3b2 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"புளூடூத் சாதன ஐகான்"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"சாதன விவரத்தை உள்ளமைக்க கிளிக் செய்யலாம்"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"அனைத்துச் சாதனங்களையும் பார்க்க கிளிக் செய்யவும்"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"புதிய சாதனத்தை இணைக்க கிளிக் செய்யவும்"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"பேட்டரி சதவீதம் தெரியவில்லை."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> உடன் இணைக்கப்பட்டுள்ளது."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"புளூடூத்தைப் பயன்படுத்துதல்"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"இணைக்கப்பட்டது"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> பேட்டரி"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • வேகமாகச் சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"சமூகப் பயிற்சியைத் தொடங்க இடதுபுறம் ஸ்வைப் செய்யுங்கள்"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"விட்ஜெட் எடிட்டரைத் திறக்கும்"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"விட்ஜெட்டை அகற்றும்"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"விட்ஜெட்டைச் சேர்"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் பயன்படுத்தப்படுகிறது"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"கீபோர்டு பேக்லைட்"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"நிலை, %2$d இல் %1$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index bfa40e8..8ec3905 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"బ్లూటూత్ పరికర చిహ్నం"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"పరికర వివరాలను కాన్ఫిగర్ చేయడానికి క్లిక్ చేయండి"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"అన్ని పరికరాలను చూడటానికి క్లిక్ చేయండి"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"కొత్త పరికరాన్ని పెయిర్ చేయడానికి క్లిక్ చేయండి"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"బ్యాటరీ శాతం తెలియదు."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ చేయబడింది"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్‌కనెక్ట్ చేయండి"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్‌సెట్"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"కమ్యూనల్ ట్యుటోరియల్‌ను ప్రారంభించడానికి ఎడమ వైపునకు స్వైప్ చేయండి"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"విడ్జెట్ ఎడిటర్‌ను తెరవండి"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"విడ్జెట్‌ను తీసివేయండి"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"విడ్జెట్‌ను జోడించండి"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్‌డౌన్ మెనూ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్‌లోని అన్ని యాప్‌లు మరియు డేటా తొలగించబడతాయి."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ద్వారా ఇటీవల వినియోగించబడింది"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా వినియోగంలో ఉంది"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా ఇటీవల ఉపయోగించబడింది"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"కీబోర్డ్ బ్యాక్‌లైట్"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dలో %1$dవ స్థాయి"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f967674..2f13280 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ไอคอนอุปกรณ์บลูทูธ"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"คลิกเพื่อกำหนดค่ารายละเอียดอุปกรณ์"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"คลิกเพื่อดูอุปกรณ์ทั้งหมด"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"คลิกเพื่อจับคู่อุปกรณ์ใหม่"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ไม่ทราบเปอร์เซ็นต์แบตเตอรี่"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"เชื่อมต่อกับ <xliff:g id="CAST">%s</xliff:g>"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ใช้บลูทูธ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"เชื่อมต่อแล้ว"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"บันทึกแล้ว"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างเร็ว • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ปัดไปทางซ้ายเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"เปิดเครื่องมือแก้ไขวิดเจ็ต"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"นำวิดเจ็ตออก"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"เพิ่มวิดเจ็ต"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ใช้ล่าสุดโดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"ใช้อยู่โดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ใช้ล่าสุดโดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ไฟแบ็กไลต์ของแป้นพิมพ์"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"ระดับที่ %1$d จาก %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 56565e6ace..11c75c6 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icon ng Bluetooth device"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"I-click para i-configure ang detalye ng device"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"I-click para tingnan ang lahat ng device"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"I-click para magpares ng bagong device"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Hindi alam ang porsyento ng baterya."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Nakakonekta sa <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> na baterya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabilis na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Mag-swipe pakaliwa para simulan ang communal na tutorial"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buksan ang editor ng widget"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Mag-alis ng widget"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Idagdag ang Widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kamakailang ginamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kamakailang ginamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Backlight ng keyboard"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d sa %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 89c4001..3f578d2 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth cihaz simgesi"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cihaz ayrıntılarını yapılandırmak için tıklayın"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Tüm cihazları görmek için tıklayın"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yeni cihaz eşlemek için tıklayın"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pil yüzdesi bilinmiyor."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> bağlantısı kuruldu."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hızlı şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ortak eğitimi başlatmak için sola kaydırın"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget düzenleyiciyi açın"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Widget kaldır"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget Ekle"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) tarafından kullanıldı"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanılıyor"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanıldı"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klavye aydınlatması"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Seviye %1$d / %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index a83d8bb..90dc097 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок пристрою з Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Натисніть, щоб змінити налаштування пристрою"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Натисніть, щоб переглянути всі пристрої"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Натисніть, щоб підключити новий пристрій"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Під’єднано до пристрою <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Збережено"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> заряду акумулятора"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -396,11 +400,11 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Швидке заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Проведіть пальцем уліво, щоб відкрити спільний навчальний посібник"</string>
+    <!-- no translation found for button_to_open_widget_editor (5599945944349057600) -->
     <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Вилучити віджет"</string>
+    <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
@@ -1210,8 +1214,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Використовується додатком <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Підсвічування клавіатури"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Рівень %1$d з %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 7a41e9f..137c8ab 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"بلوٹوتھ آلے کا آئیکن"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"آلہ کی تفصیل کو کنفیگر کرنے کے لیے کلک کریں"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"تمام آلات دیکھنے کے لیے کلک کریں"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"نئے آلے کا جوڑا بنانے کے لیے کلک کریں"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"بیٹری کی فیصد نامعلوم ہے۔"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> سے منسلک ہے۔"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"استعمال کریں"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"منسلک ہے"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ ہے"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"کمیونل ٹیوٹوریل شروع کرنے کے لیے بائیں سوائپ کریں"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ویجیٹ ایڈیٹر کو کھولیں"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"ویجیٹ ہٹائیں"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ویجٹ شامل کریں"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) کے ذریعے حال ہی میں استعمال کیا گیا"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) کے زیر استعمال"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) کے ذریعے حال ہی میں استعمال کیا گیا"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"کی بورڈ بیک لائٹ"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"‏%2$d میں سے ‎%1$d کا لیول"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index d01ed19..601d773 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth qurilma belgisi"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Qurilma haqida tafsilotlarni oʻzgartirish uchun bosing"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Barcha qurilmalarni koʻrish uchun bosing"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yangi qurilmani ulash uchun bosing"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareya quvvati foizi nomaʼlum."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Bunga ulangan: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulandi"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Tez quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Qoʻllanma bilan tanishish uchun chapga suring"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidjet muharririni ochish"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Vidjetni olib tashlash"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Vidjet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Yaqinda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ishlatgan"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ishlatmoqda"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Yaqinda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ishlatgan"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatura orqa yoritkichi"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Daraja: %1$d / %2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 5a10076..b67c789 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Biểu tượng thiết bị Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Nhấp để định cấu hình thông tin thiết bị"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Nhấp để xem tất cả các thiết bị"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Nhấp để ghép nối thiết bị mới"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Tỷ lệ phần trăm pin không xác định."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Đã kết nối với <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> pin"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc nhanh • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Vuốt sang trái để bắt đầu xem hướng dẫn chung"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Mở trình chỉnh sửa tiện ích"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Xoá tiện ích"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Thêm tiện ích"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) đã dùng gần đây"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đang dùng"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đã dùng gần đây"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Đèn nền bàn phím"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Độ sáng %1$d/%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 92dac52..576cbc7 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"蓝牙设备图标"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"点击以配置设备详情"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"点击即可查看所有设备"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"点击即可配对新设备"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"电池电量百分比未知。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"已连接到 <xliff:g id="CAST">%s</xliff:g>。"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用蓝牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已连接"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已保存"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在快速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑动即可启动公共教程"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"打开微件编辑器"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"移除微件"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"添加微件"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”最近使用过(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正在使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”最近使用过(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"键盘背光"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"第 %1$d 级,共 %2$d 级"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index c94c2cb..bcf7478 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"藍牙裝置圖示"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"按一下即可設定裝置詳情"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"㩒一下就可以睇所有裝置"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"㩒一下就可以配對新裝置"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"已連接至 <xliff:g id="CAST">%s</xliff:g>。"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連接"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -396,12 +400,10 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
-    <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可開始共用教學課程"</string>
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"移除小工具"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"新增小工具"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
@@ -1210,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近使用過此權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 正在使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近使用過此權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"鍵盤背光"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"第 %1$d 級,共 %2$d 級"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index e05d309..76d11ec 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"「藍牙裝置」圖示"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"按一下即可設定裝置詳細資料"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"按一下即可查看所有裝置"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"按一下即可配對新裝置"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電池電量不明。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"已連線至 <xliff:g id="CAST">%s</xliff:g>。"</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連線"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -396,11 +400,11 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
-    <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) -->
+    <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可啟動通用教學課程"</string>
+    <!-- no translation found for button_to_open_widget_editor (5599945944349057600) -->
     <skip />
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"移除小工具"</string>
+    <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
@@ -1210,8 +1214,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近用過這項權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"正由「<xliff:g id="APP_NAME">%1$s</xliff:g>」使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近用過這項權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"鍵盤背光"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"第 %1$d 級,共 %2$d 級"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index db2ba79..591a63e 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -197,6 +197,8 @@
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
     <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Isithonjana sedivayisi ye-Bluetooth"</string>
     <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Chofoza ukuze ulungiselele imininingwane yedivayisi"</string>
+    <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Chofoza ukuze ubone wonke amadivayisi"</string>
+    <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Chofoza ukuze ubhangqe idivayisi entsha"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Iphesenti lebhethri alaziwa."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Ixhumeke ku-<xliff:g id="CAST">%s</xliff:g>."</string>
@@ -255,6 +257,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa i-Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
@@ -397,10 +401,9 @@
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swayiphela kwesokunxele ukuze uqale okokufundisa komphakathi"</string>
-    <!-- no translation found for button_to_open_widget_picker (8007261659745030810) -->
-    <skip />
-    <!-- no translation found for button_to_remove_widget (1511255853677835341) -->
-    <skip />
+    <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vula isihleli sewijethi"</string>
+    <string name="button_to_remove_widget" msgid="1511255853677835341">"Susa iwijethi"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Engeza Iwijethi"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
@@ -1209,8 +1212,6 @@
     <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
     <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Isetshenziswa yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
     <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
-    <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) -->
-    <skip />
-    <!-- no translation found for keyboard_backlight_value (7336398765584393538) -->
-    <skip />
+    <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Ilambu lekhibhodi"</string>
+    <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ileveli %1$d ka-%2$d"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0620355..e124aa0 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -119,9 +119,8 @@
 
     <!-- Keyboard shortcuts colors -->
     <color name="ksh_application_group_color">#fff44336</color>
-    <color name="ksh_key_item_color">@color/material_grey_600</color>
-    <color name="ksh_key_item_background">@color/material_grey_100</color>
-    <color name="ksh_key_item_new_background">@color/transparent</color>
+    <color name="ksh_key_item_color">?androidprv:attr/materialColorOnSurfaceVariant</color>
+    <color name="ksh_key_item_background">?androidprv:attr/materialColorSurfaceContainerHighest</color>
 
     <color name="instant_apps_color">#ff4d5a64</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 30392d2..03960d5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -940,8 +940,11 @@
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">@dimen/match_parent</dimen>
     <dimen name="ksh_item_text_size">14sp</dimen>
-    <dimen name="ksh_item_padding">4dp</dimen>
+    <dimen name="ksh_item_padding">0dp</dimen>
     <dimen name="ksh_item_margin_start">4dp</dimen>
+    <dimen name="ksh_icon_scaled_size">18dp</dimen>
+    <dimen name="ksh_key_view_padding_vertical">4dp</dimen>
+    <dimen name="ksh_key_view_padding_horizontal">12dp</dimen>
 
     <!-- The size of corner radius of the arrow in the onboarding toast. -->
     <dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 74d435d..1838795 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -230,7 +230,6 @@
 
     <!-- Communal mode -->
     <item type="id" name="communal_hub" />
-    <item type="id" name="communal_widget_wrapper" />
 
     <!-- Values assigned to the views in Biometrics Prompt -->
     <item type="id" name="pin_pad"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index daf6cb3..f49d2a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1773,13 +1773,13 @@
     <!-- Name used to refer to the "Back" key on the keyboard. -->
     <string name="keyboard_key_back">Back</string>
     <!-- Name used to refer to the "Up" arrow key on the keyboard. -->
-    <string name="keyboard_key_dpad_up">Up</string>
+    <string name="keyboard_key_dpad_up">Up arrow</string>
     <!-- Name used to refer to the "Down" arrow key on the keyboard. -->
-    <string name="keyboard_key_dpad_down">Down</string>
+    <string name="keyboard_key_dpad_down">Down arrow</string>
     <!-- Name used to refer to the "Left" arrow key on the keyboard. -->
-    <string name="keyboard_key_dpad_left">Left</string>
+    <string name="keyboard_key_dpad_left">Left arrow</string>
     <!-- Name used to refer to the "Right" arrow key on the keyboard. -->
-    <string name="keyboard_key_dpad_right">Right</string>
+    <string name="keyboard_key_dpad_right">Right arrow</string>
     <!-- Name used to refer to the "Center" arrow key on the keyboard. -->
     <string name="keyboard_key_dpad_center">Center</string>
     <!-- Name used to refer to the "Tab" key on the keyboard. -->
@@ -1834,9 +1834,11 @@
     <string name="keyboard_shortcut_group_system_shortcuts_helper">Keyboard Shortcuts</string>
     <!-- User visible title for the keyboard shortcut that switches to the next hardware keyboard layout. -->
     <string name="keyboard_shortcut_group_system_switch_input">Switch keyboard layout</string>
+    <!-- User visible string that joins different shortcuts in a list, e.g. shortcut1 "or" shortcut2 "or" ... -->
+    <string  name="keyboard_shortcut_join">or</string>
 
-    <!-- Content description for the clear text button in shortcut search list. [CHAR LIMIT=NONE] -->
-    <string name="keyboard_shortcut_clear_text">Clear text</string>
+    <!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] -->
+    <string name="keyboard_shortcut_clear_text">Clear search query</string>
     <!-- The title for keyboard shortcut search list [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_list_title">Shortcuts</string>
     <!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] -->
@@ -1852,52 +1854,63 @@
     <!-- The title of current app category in shortcut search list. [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_category_current_app">Current app</string>
 
+    <!-- A11y message when showing keyboard shortcut search results. [CHAR LIMT=NONE] -->
+    <string name="keyboard_shortcut_a11y_show_search_results">Showing search results</string>
+    <!-- A11y message when filtering to "system" keyboard shortcuts.  [CHAR LIMT=NONE] -->
+    <string name="keyboard_shortcut_a11y_filter_system">Showing system shortcuts</string>
+    <!-- A11y message when filtering to "input" keyboard shortcuts.  [CHAR LIMT=NONE] -->
+    <string name="keyboard_shortcut_a11y_filter_input">Showing input shortcuts</string>
+    <!-- A11y message when filtering to "app opening" keyboard shortcuts.  [CHAR LIMT=NONE] -->
+    <string name="keyboard_shortcut_a11y_filter_open_apps">Showing shortcuts that open apps</string>
+    <!-- A11y message when filtering to "current app" keyboard shortcuts.  [CHAR LIMT=NONE] -->
+    <string name="keyboard_shortcut_a11y_filter_current_app">Showing shortcuts for the current app</string>
+
     <!-- User visible title for the keyboard shortcut that triggers the notification shade. [CHAR LIMIT=70] -->
-    <string name="group_system_access_notification_shade">Access notification shade</string>
+    <string name="group_system_access_notification_shade">View notifications</string>
     <!-- User visible title for the keyboard shortcut that takes a full screenshot. [CHAR LIMIT=70] -->
-    <string name="group_system_full_screenshot">Take a full screenshot</string>
+    <string name="group_system_full_screenshot">Take screenshot</string>
     <!-- User visible title for the keyboard shortcut that access list of system / apps shortcuts. [CHAR LIMIT=70] -->
-    <string name="group_system_access_system_app_shortcuts">Access list of system / apps shortcuts</string>
+    <string name="group_system_access_system_app_shortcuts">Show shortcuts</string>
     <!-- User visible title for the keyboard shortcut that goes back to previous state. [CHAR LIMIT=70] -->
-    <string name="group_system_go_back">Back: go back to previous state (back button)</string>
+    <string name="group_system_go_back">Go back</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the home screen. [CHAR LIMIT=70] -->
-    <string name="group_system_access_home_screen">Access home screen</string>
+    <string name="group_system_access_home_screen">Go to home screen</string>
     <!-- User visible title for the keyboard shortcut that triggers overview of open apps. [CHAR LIMIT=70] -->
-    <string name="group_system_overview_open_apps">Overview of open apps</string>
+    <string name="group_system_overview_open_apps">View recent apps</string>
     <!-- User visible title for the keyboard shortcut that cycles through recent apps (forward). [CHAR LIMIT=70] -->
-    <string name="group_system_cycle_forward">Cycle through recent apps (forward)</string>
+    <string name="group_system_cycle_forward">Cycle forward through recent apps</string>
     <!-- User visible title for the keyboard shortcut that cycles through recent apps (back). [CHAR LIMIT=70] -->
-    <string name="group_system_cycle_back">Cycle through recent apps (back)</string>
+    <string name="group_system_cycle_back">Cycle backward through recent apps</string>
     <!-- User visible title for the keyboard shortcut that accesses list of all apps and search. [CHAR LIMIT=70] -->
-    <string name="group_system_access_all_apps_search">Access list of all apps and search (i.e. Search/Launcher)</string>
+    <string name="group_system_access_all_apps_search">Open apps list</string>
     <!-- User visible title for the keyboard shortcut that hides and (re)showes taskbar. [CHAR LIMIT=70] -->
-    <string name="group_system_hide_reshow_taskbar">Hide and (re)show taskbar</string>
-    <!-- User visible title for the keyboard shortcut that accesses system settings. [CHAR LIMIT=70] -->
-    <string name="group_system_access_system_settings">Access system settings</string>
-    <!-- User visible title for the keyboard shortcut that accesses Google Assistant. [CHAR LIMIT=70] -->
-    <string name="group_system_access_google_assistant">Access Google Assistant</string>
+    <string name="group_system_hide_reshow_taskbar">Show taskbar</string>
+    <!-- User visible title for the keyboard shortcut that accesses [system] settings. [CHAR LIMIT=70] -->
+    <string name="group_system_access_system_settings">Open settings</string>
+    <!-- User visible title for the keyboard shortcut that accesses Assistant app. [CHAR LIMIT=70] -->
+    <string name="group_system_access_google_assistant">Open assistant</string>
     <!-- User visible title for the keyboard shortcut that locks screen. [CHAR LIMIT=70] -->
     <string name="group_system_lock_screen">Lock screen</string>
     <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
-    <string name="group_system_quick_memo">Pull up Notes app for quick memo</string>
+    <string name="group_system_quick_memo">Open notes</string>
 
     <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string>
     <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] -->
-    <string name="system_multitasking_rhs">Enter Split screen with current app to RHS</string>
+    <string name="system_multitasking_rhs">Enter split screen with current app to RHS</string>
     <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] -->
-    <string name="system_multitasking_lhs">Enter Split screen with current app to LHS</string>
+    <string name="system_multitasking_lhs">Enter split screen with current app to LHS</string>
     <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
-    <string name="system_multitasking_full_screen">Switch from Split screen to full screen</string>
+    <string name="system_multitasking_full_screen">Switch from split screen to full screen</string>
     <!-- User visible title for the keyboard shortcut that replaces an app from one to another during split screen [CHAR LIMIT=70] -->
-    <string name="system_multitasking_replace">During Split screen: replace an app from one to another</string>
+    <string name="system_multitasking_replace">During split screen: replace an app from one to another</string>
 
     <!-- User visible title for the input keyboard shortcuts list. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_input">Input</string>
     <!-- User visible title for the keyboard shortcut that switches input language (next language). [CHAR LIMIT=70] -->
-    <string name="input_switch_input_language_next">Switch input language (next language)</string>
+    <string name="input_switch_input_language_next">Switch to next language</string>
     <!-- User visible title for the keyboard shortcut that switches input language (previous language). [CHAR LIMIT=70] -->
-    <string name="input_switch_input_language_previous">Switch input language (previous language)</string>
+    <string name="input_switch_input_language_previous">Switch to previous language</string>
     <!-- User visible title for the keyboard shortcut that accesses emoji. [CHAR LIMIT=70] -->
     <string name="input_access_emoji">Access emoji</string>
     <!-- User visible title for the keyboard shortcut that accesses voice typing. [CHAR LIMIT=70] -->
@@ -1905,14 +1918,14 @@
 
     <!-- User visible title for the system-wide applications keyboard shortcuts list. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_applications">Applications</string>
-    <!-- User visible title for the keyboard shortcut that takes the user to the assist app. [CHAR LIMIT=70] -->
-    <string name="keyboard_shortcut_group_applications_assist">Assist</string>
+    <!-- User visible title for the keyboard shortcut that takes the user to the assistant app. [CHAR LIMIT=70] -->
+    <string name="keyboard_shortcut_group_applications_assist">Assistant</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] -->
-    <string name="keyboard_shortcut_group_applications_browser">Browser (Chrome as default)</string>
+    <string name="keyboard_shortcut_group_applications_browser">Browser</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] -->
-    <string name="keyboard_shortcut_group_applications_email">Email (Gmail as default)</string>
+    <string name="keyboard_shortcut_group_applications_email">Email</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_applications_sms">SMS</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2117714..c48dd9d 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -420,6 +420,11 @@
     <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match -->
     <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
         <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowAnimationStyle">@style/Animation.BrightnessDialog</item>
+    </style>
+
+    <style name="Animation.BrightnessDialog">
+        <item name="android:windowExitAnimation">@anim/instant_fade_out</item>
     </style>
 
     <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
@@ -1496,10 +1501,14 @@
 
     <style name="ShortCutButton" parent="@android:style/Widget.Material.Button">
         <item name="android:background">@drawable/shortcut_button_colored</item>
-        <item name="android:stateListAnimator">@null</item>
         <item name="android:textSize">16sp</item>
-        <item name="android:padding">4dp</item>
-        <item name="android:textColor">?androidprv:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:layout_marginEnd">12dp</item>
+        <item name="android:paddingLeft">24dp</item>
+        <item name="android:paddingRight">24dp</item>
+        <item name="android:minHeight">40dp</item>
+        <item name="android:stateListAnimator">@*android:anim/flat_button_state_list_anim_material</item>
+        <item name="android:pointerIcon">arrow</item>
     </style>
 
     <style name="ShortcutHorizontalDivider">
@@ -1509,10 +1518,6 @@
         <item name="android:background">?android:attr/dividerHorizontal</item>
     </style>
 
-    <style name="ShortcutItemBackground">
-        <item name="android:background">@color/ksh_key_item_new_background</item>
-    </style>
-
     <style name="LongPressLockScreenAnimation">
         <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item>
         <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item>
@@ -1535,4 +1540,4 @@
     <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
         <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
     </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
index c9e57b4..b33f6fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -32,8 +32,7 @@
     override val isHomeActivity: Boolean?
         get() = _isHomeActivity
 
-    private var _isHomeActivity: Boolean? = null
-
+    @Volatile private var _isHomeActivity: Boolean? = null
 
     override fun init() {
         _isHomeActivity = activityManager.isOnHomeActivity()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
index 3b8d318..baa8889 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
@@ -18,18 +18,19 @@
 import android.hardware.devicestate.DeviceStateManager
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class DeviceStateManagerFoldProvider @Inject constructor(
-    private val deviceStateManager: DeviceStateManager,
-    private val context: Context
-) : FoldProvider {
+class DeviceStateManagerFoldProvider
+@Inject
+constructor(private val deviceStateManager: DeviceStateManager, private val context: Context) :
+    FoldProvider {
 
-    private val callbacks: MutableMap<FoldCallback,
-            DeviceStateManager.DeviceStateCallback> = hashMapOf()
+    private val callbacks =
+        ConcurrentHashMap<FoldCallback, DeviceStateManager.DeviceStateCallback>()
 
     override fun registerCallback(callback: FoldCallback, executor: Executor) {
         val listener = FoldStateListener(context, callback)
@@ -39,13 +40,9 @@
 
     override fun unregisterCallback(callback: FoldCallback) {
         val listener = callbacks.remove(callback)
-        listener?.let {
-            deviceStateManager.unregisterCallback(it)
-        }
+        listener?.let { deviceStateManager.unregisterCallback(it) }
     }
 
-    private inner class FoldStateListener(
-        context: Context,
-        listener: FoldCallback
-    ) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
+    private inner class FoldStateListener(context: Context, listener: FoldCallback) :
+        DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 7b67e3f..7af9917 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -15,24 +15,29 @@
 package com.android.systemui.unfold.system
 
 import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import java.util.concurrent.Executor
+import javax.inject.Singleton
 
 /**
- * Dagger module with system-only dependencies for the unfold animation.
- * The code that is used to calculate unfold transition progress
- * depends on some hidden APIs that are not available in normal
- * apps. In order to re-use this code and use alternative implementations
- * of these classes in other apps and hidden APIs here.
+ * Dagger module with system-only dependencies for the unfold animation. The code that is used to
+ * calculate unfold transition progress depends on some hidden APIs that are not available in normal
+ * apps. In order to re-use this code and use alternative implementations of these classes in other
+ * apps and hidden APIs here.
  */
 @Module
 abstract class SystemUnfoldSharedModule {
@@ -61,4 +66,22 @@
     @Binds
     @UnfoldSingleThreadBg
     abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
+
+    companion object {
+        @Provides
+        @UnfoldBg
+        @Singleton
+        fun unfoldBgProgressHandler(@UnfoldBg looper: Looper): Handler {
+            return Handler(looper)
+        }
+
+        @Provides
+        @UnfoldBg
+        @Singleton
+        fun provideBgLooper(): Looper {
+            return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND)
+                .apply { start() }
+                .looper
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 3757274..1ee58de 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -317,7 +317,7 @@
         }
 
         keyguardUpdateMonitor?.let {
-            val anyFaceEnrolled = it.isFaceEnrolled
+            val anyFaceEnrolled = it.isFaceEnabledAndEnrolled
             val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible(
                     selectedUserInteractor.getSelectedUserId())
             val udfpsEnrolled = it.isUdfpsEnrolled
@@ -372,7 +372,7 @@
         keyguardUpdateMonitor?.let {
             pw.println("   shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
                     "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
-            pw.println("   faceEnrolled=${it.isFaceEnrolled}")
+            pw.println("   isFaceEnabledAndEnrolled=${it.isFaceEnabledAndEnrolled}")
             pw.println("   fpUnlockPossible=${
                 it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}")
             pw.println("   udfpsEnrolled=${it.isUdfpsEnrolled}")
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dedac55..54cb501 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,6 +39,7 @@
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.common.ui.ConfigurationState;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlagsClassic;
@@ -78,6 +79,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -136,6 +138,7 @@
     private KeyguardInteractor mKeyguardInteractor;
     private KeyguardClockInteractor mKeyguardClockInteractor;
     private final DelayableExecutor mUiExecutor;
+    private final Executor mBgExecutor;
     private boolean mCanShowDoubleLineClock = true;
     private DisposableHandle mAodIconsBindHandle;
     @Nullable private NotificationIconContainer mAodIconContainer;
@@ -186,6 +189,7 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SecureSettings secureSettings,
             @Main DelayableExecutor uiExecutor,
+            @Background Executor bgExecutor,
             DumpManager dumpManager,
             ClockEventController clockEventController,
             @KeyguardClockLog LogBuffer logBuffer,
@@ -209,6 +213,7 @@
         mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
         mSecureSettings = secureSettings;
         mUiExecutor = uiExecutor;
+        mBgExecutor = bgExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mDumpManager = dumpManager;
         mClockEventController = clockEventController;
@@ -328,19 +333,22 @@
         updateAodIcons();
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
 
-        mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
-                false, /* notifyForDescendants */
-                mDoubleLineClockObserver,
-                UserHandle.USER_ALL
-        );
+        mBgExecutor.execute(() -> {
+            mSecureSettings.registerContentObserverForUser(
+                    Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+                    false, /* notifyForDescendants */
+                    mDoubleLineClockObserver,
+                    UserHandle.USER_ALL
+            );
 
-        mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
-                false, /* notifyForDescendants */
-                mShowWeatherObserver,
-                UserHandle.USER_ALL
-        );
+            mSecureSettings.registerContentObserverForUser(
+                    Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+                    false, /* notifyForDescendants */
+                    mShowWeatherObserver,
+                    UserHandle.USER_ALL
+            );
+        });
+
         updateDoubleLineClock();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
@@ -376,14 +384,21 @@
         }
     }
 
+    @Nullable
+    View getAodNotifIconContainer() {
+        return mAodIconContainer;
+    }
+
     @Override
     protected void onViewDetached() {
         mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
         mClockEventController.unregisterListeners();
         setClock(null);
 
-        mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
-        mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
+        mBgExecutor.execute(() -> {
+            mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
+            mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
+        });
 
         mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -629,6 +644,7 @@
                 }
             } else {
                 mNotificationIconAreaController.setupAodIcons(nic);
+                mAodIconContainer = nic;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
deleted file mode 100644
index d7019b5..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.annotation.CurrentTimeMillisLong
-import com.android.systemui.common.buffer.RingBuffer
-import com.android.systemui.dump.DumpsysTableLogger
-import com.android.systemui.dump.Row
-
-/** Verbose debug information associated. */
-data class KeyguardFaceListenModel(
-    @CurrentTimeMillisLong override var timeMillis: Long = 0L,
-    override var userId: Int = 0,
-    override var listening: Boolean = false,
-    // keep sorted
-    var allowedDisplayStateWhileAwake: Boolean = false,
-    var alternateBouncerShowing: Boolean = false,
-    var authInterruptActive: Boolean = false,
-    var biometricSettingEnabledForUser: Boolean = false,
-    var bouncerFullyShown: Boolean = false,
-    var faceAndFpNotAuthenticated: Boolean = false,
-    var faceAuthAllowed: Boolean = false,
-    var faceDisabled: Boolean = false,
-    var faceLockedOut: Boolean = false,
-    var goingToSleep: Boolean = false,
-    var keyguardAwake: Boolean = false,
-    var keyguardGoingAway: Boolean = false,
-    var listeningForFaceAssistant: Boolean = false,
-    var occludingAppRequestingFaceAuth: Boolean = false,
-    var postureAllowsListening: Boolean = false,
-    var secureCameraLaunched: Boolean = false,
-    var supportsDetect: Boolean = false,
-    var switchingUser: Boolean = false,
-    var systemUser: Boolean = false,
-    var udfpsFingerDown: Boolean = false,
-    var userNotTrustedOrDetectionIsNeeded: Boolean = false,
-) : KeyguardListenModel() {
-
-    /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
-    val asStringList: List<String> by lazy {
-        listOf(
-            DATE_FORMAT.format(timeMillis),
-            timeMillis.toString(),
-            userId.toString(),
-            listening.toString(),
-            // keep sorted
-            allowedDisplayStateWhileAwake.toString(),
-            alternateBouncerShowing.toString(),
-            authInterruptActive.toString(),
-            biometricSettingEnabledForUser.toString(),
-            bouncerFullyShown.toString(),
-            faceAndFpNotAuthenticated.toString(),
-            faceAuthAllowed.toString(),
-            faceDisabled.toString(),
-            faceLockedOut.toString(),
-            goingToSleep.toString(),
-            keyguardAwake.toString(),
-            keyguardGoingAway.toString(),
-            listeningForFaceAssistant.toString(),
-            occludingAppRequestingFaceAuth.toString(),
-            postureAllowsListening.toString(),
-            secureCameraLaunched.toString(),
-            supportsDetect.toString(),
-            switchingUser.toString(),
-            systemUser.toString(),
-            udfpsFingerDown.toString(),
-            userNotTrustedOrDetectionIsNeeded.toString(),
-        )
-    }
-
-    /**
-     * [RingBuffer] to store [KeyguardFaceListenModel]. After the buffer is full, it will recycle
-     * old events.
-     *
-     * Do not use [append] to add new elements. Instead use [insert], as it will recycle if
-     * necessary.
-     */
-    class Buffer {
-        private val buffer = RingBuffer(CAPACITY) { KeyguardFaceListenModel() }
-
-        fun insert(model: KeyguardFaceListenModel) {
-            buffer.advance().apply {
-                timeMillis = model.timeMillis
-                userId = model.userId
-                listening = model.listening
-                // keep sorted
-                allowedDisplayStateWhileAwake = model.allowedDisplayStateWhileAwake
-                alternateBouncerShowing = model.alternateBouncerShowing
-                authInterruptActive = model.authInterruptActive
-                biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
-                bouncerFullyShown = model.bouncerFullyShown
-                faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
-                faceAuthAllowed = model.faceAuthAllowed
-                faceDisabled = model.faceDisabled
-                faceLockedOut = model.faceLockedOut
-                goingToSleep = model.goingToSleep
-                keyguardAwake = model.keyguardAwake
-                keyguardGoingAway = model.keyguardGoingAway
-                listeningForFaceAssistant = model.listeningForFaceAssistant
-                occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
-                postureAllowsListening = model.postureAllowsListening
-                secureCameraLaunched = model.secureCameraLaunched
-                supportsDetect = model.supportsDetect
-                switchingUser = model.switchingUser
-                systemUser = model.systemUser
-                udfpsFingerDown = model.udfpsFingerDown
-                userNotTrustedOrDetectionIsNeeded = model.userNotTrustedOrDetectionIsNeeded
-            }
-        }
-        /**
-         * Returns the content of the buffer (sorted from latest to newest).
-         *
-         * @see KeyguardFingerprintListenModel.asStringList
-         */
-        fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
-        }
-    }
-
-    companion object {
-        const val CAPACITY = 40 // number of logs to retain
-
-        /** Headers for dumping a table using [DumpsysTableLogger]. */
-        @JvmField
-        val TABLE_HEADERS =
-            listOf(
-                "timestamp",
-                "time_millis",
-                "userId",
-                "listening",
-                // keep sorted
-                "allowedDisplayStateWhileAwake",
-                "alternateBouncerShowing",
-                "authInterruptActive",
-                "biometricSettingEnabledForUser",
-                "bouncerFullyShown",
-                "faceAndFpNotAuthenticated",
-                "faceAuthAllowed",
-                "faceDisabled",
-                "faceLockedOut",
-                "goingToSleep",
-                "keyguardAwake",
-                "keyguardGoingAway",
-                "listeningForFaceAssistant",
-                "occludingAppRequestingFaceAuth",
-                "postureAllowsListening",
-                "secureCameraLaunched",
-                "supportsDetect",
-                "switchingUser",
-                "systemUser",
-                "udfpsFingerDown",
-                "userNotTrustedOrDetectionIsNeeded",
-            )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1b6112f..f706301 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -214,7 +214,6 @@
         public void onUserInput() {
             mBouncerMessageInteractor.onPrimaryBouncerUserInput();
             mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput();
-            mUpdateMonitor.cancelFaceAuth();
         }
 
         @Override
@@ -340,16 +339,11 @@
     private final SwipeListener mSwipeListener = new SwipeListener() {
         @Override
         public void onSwipeUp() {
-            if (!mUpdateMonitor.isFaceDetectionRunning()) {
-                mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
-                boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
-                        FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+            if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
                 mKeyguardSecurityCallback.userActivity();
-                if (didFaceAuthRun) {
-                    showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true);
-                }
             }
-            if (mUpdateMonitor.isFaceEnrolled()) {
+            mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
+            if (mKeyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) {
                 mUpdateMonitor.requestActiveUnlock(
                         ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                         "swipeUpOnBouncer");
@@ -755,7 +749,7 @@
         }
         mView.onResume(
                 mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()),
-                mKeyguardStateController.isFaceEnrolled());
+                mKeyguardStateController.isFaceEnrolledAndEnabled());
     }
 
     /** Sets an initial message that would override the default message */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 87d937b..4fbf077 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -84,6 +84,7 @@
         Dumpable {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     @VisibleForTesting static final String TAG = "KeyguardStatusViewController";
+    private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133;
 
     /**
      * Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -104,6 +105,10 @@
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final DozeParameters mDozeParameters;
+
+    private View mStatusArea = null;
+    private ValueAnimator mStatusAreaHeightAnimator = null;
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
@@ -123,6 +128,46 @@
                 }
             };
 
+    private final View.OnLayoutChangeListener mStatusAreaLayoutChangeListener =
+            new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v,
+                        int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom
+                ) {
+                    if (!mDozeParameters.getAlwaysOn()) {
+                        return;
+                    }
+
+                    int oldHeight = oldBottom - oldTop;
+                    int diff = v.getHeight() - oldHeight;
+                    if (diff == 0) {
+                        return;
+                    }
+
+                    int startValue = -1 * diff;
+                    long duration = STATUS_AREA_HEIGHT_ANIMATION_MILLIS;
+                    if (mStatusAreaHeightAnimator != null
+                            && mStatusAreaHeightAnimator.isRunning()) {
+                        duration += mStatusAreaHeightAnimator.getDuration()
+                                - mStatusAreaHeightAnimator.getCurrentPlayTime();
+                        startValue += (int) mStatusAreaHeightAnimator.getAnimatedValue();
+                        mStatusAreaHeightAnimator.cancel();
+                        mStatusAreaHeightAnimator = null;
+                    }
+
+                    mStatusAreaHeightAnimator = ValueAnimator.ofInt(startValue, 0);
+                    mStatusAreaHeightAnimator.setDuration(duration);
+                    final View nic = mKeyguardClockSwitchController.getAodNotifIconContainer();
+                    if (nic != null) {
+                        mStatusAreaHeightAnimator.addUpdateListener(anim -> {
+                            nic.setTranslationY((int) anim.getAnimatedValue());
+                        });
+                    }
+                    mStatusAreaHeightAnimator.start();
+                }
+            };
+
     @Inject
     public KeyguardStatusViewController(
             KeyguardStatusView keyguardStatusView,
@@ -144,6 +189,7 @@
         mKeyguardClockSwitchController = keyguardClockSwitchController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
+        mDozeParameters = dozeParameters;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
                 logger.getBuffer());
@@ -218,12 +264,15 @@
 
     @Override
     protected void onViewAttached() {
+        mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+        mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
     @Override
     protected void onViewDetached() {
+        mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
     }
@@ -293,9 +342,15 @@
     /**
      * Get the height of the keyguard status view without the notification icon area, as that's
      * only visible on AOD.
+     *
+     * We internally animate height changes to the status area to prevent discontinuities in the
+     * doze animation introduced by the height suddenly changing due to smartpace.
      */
     public int getLockscreenHeight() {
-        return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
+        int heightAnimValue = mStatusAreaHeightAnimator == null ? 0 :
+                (int) mStatusAreaHeightAnimator.getAnimatedValue();
+        return mView.getHeight() + heightAnimValue
+                - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index baab637..c5bb099 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -34,48 +34,11 @@
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
-import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FP_LOCKED_OUT;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_TRUST_ENABLED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_TRUST_DISABLED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_CAMERA_LAUNCHED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_FP_AUTHENTICATED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_GOING_TO_SLEEP;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_RESET;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_POSTURE_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
@@ -104,10 +67,7 @@
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
@@ -137,7 +97,6 @@
 import android.text.TextUtils;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.view.Display;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -161,7 +120,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
@@ -172,12 +130,10 @@
 import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -318,7 +274,6 @@
     private final boolean mIsSystemUser;
     private final AuthController mAuthController;
     private final UiEventLogger mUiEventLogger;
-    private final Set<Integer> mFaceAcquiredInfoIgnoreList;
     private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
     private final PackageManager mPackageManager;
     private int mStatusBarState;
@@ -339,26 +294,6 @@
             }
         }
     };
-    private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() {
-        @Override
-        public void onDisplayChanged(int displayId) {
-            if (displayId != Display.DEFAULT_DISPLAY) {
-                return;
-            }
-
-            if (mWakefulness.getWakefulness() == WAKEFULNESS_AWAKE
-                    && mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState()
-                    == Display.STATE_OFF) {
-                mAllowedDisplayStateWhileAwakeForFaceAuth = false;
-                updateFaceListeningState(
-                        BIOMETRIC_ACTION_STOP,
-                        FACE_AUTH_DISPLAY_OFF
-                );
-            } else {
-                mAllowedDisplayStateWhileAwakeForFaceAuth = true;
-            }
-        }
-    };
     private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
 
     HashMap<Integer, SimData> mSimDatas = new HashMap<>();
@@ -377,9 +312,7 @@
     private boolean mNeedsSlowUnlockTransition;
     private boolean mAssistantVisible;
     private boolean mOccludingAppRequestingFp;
-    private boolean mOccludingAppRequestingFace;
     private boolean mSecureCameraLaunched;
-    private boolean mAllowedDisplayStateWhileAwakeForFaceAuth = true;
     private boolean mBiometricPromptShowing;
     @VisibleForTesting
     protected boolean mTelephonyCapable;
@@ -409,7 +342,6 @@
     private final TrustManager mTrustManager;
     private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
-    private final DevicePostureController mPostureController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
@@ -422,13 +354,9 @@
     @Nullable
     private final FingerprintManager mFpm;
     @Nullable
-    private final FaceManager mFaceManager;
-    @Nullable
     private KeyguardFaceAuthInteractor mFaceAuthInteractor;
     private final TaskStackChangeListeners mTaskStackChangeListeners;
     private final IActivityTaskManager mActivityTaskManager;
-    private final WakefulnessLifecycle mWakefulness;
-    private final DisplayTracker mDisplayTracker;
     private final SelectedUserInteractor mSelectedUserInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
@@ -439,11 +367,9 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     @VisibleForTesting
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private int mPostureState = DEVICE_POSTURE_UNKNOWN;
     private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
 
     /**
@@ -455,7 +381,6 @@
 
     // If the HAL dies or is unable to authenticate, keyguard should retry after a short delay
     private int mHardwareFingerprintUnavailableRetryCount = 0;
-    private int mHardwareFaceUnavailableRetryCount = 0;
     private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
     private static final int HAL_ERROR_RETRY_MAX = 20;
 
@@ -465,7 +390,6 @@
     @VisibleForTesting
     protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
 
-    private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
     private final Provider<SessionTracker> mSessionTrackerProvider;
 
     @VisibleForTesting
@@ -481,8 +405,7 @@
                 public void onChanged(boolean enabled, int userId) {
                     mHandler.post(() -> {
                         mBiometricEnabledForUser.put(userId, enabled);
-                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                                FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD);
+                        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
                     });
                 }
             };
@@ -525,15 +448,11 @@
 
     private final KeyguardFingerprintListenModel.Buffer mFingerprintListenBuffer =
             new KeyguardFingerprintListenModel.Buffer();
-    private final KeyguardFaceListenModel.Buffer mFaceListenBuffer =
-            new KeyguardFaceListenModel.Buffer();
     private final KeyguardActiveUnlockModel.Buffer mActiveUnlockTriggerBuffer =
             new KeyguardActiveUnlockModel.Buffer();
 
     @VisibleForTesting
     SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
-    @VisibleForTesting
-    SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>();
 
     private static int sCurrentUser;
 
@@ -561,11 +480,9 @@
         // authenticating.  TrustManager sends an onTrustChanged whenever a user unlocks keyguard,
         // for this reason we need to make sure to not authenticate.
         if (wasTrusted == enabled || enabled) {
-            updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
-                    FACE_AUTH_STOPPED_TRUST_ENABLED);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
         } else {
-            updateBiometricListeningState(BIOMETRIC_ACTION_START,
-                    FACE_AUTH_TRIGGERED_TRUST_DISABLED);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_START);
         }
 
         mLogger.logTrustChanged(wasTrusted, enabled, userId);
@@ -807,8 +724,6 @@
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
         if (mKeyguardGoingAway) {
-            updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                    FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
@@ -855,29 +770,12 @@
             }
         }
 
-        if (occlusionChanged) {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
-        } else if (showingChanged) {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+        if (occlusionChanged || showingChanged) {
+            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
     }
 
     /**
-     * Request to listen for face authentication when an app is occluding keyguard.
-     *
-     * @param request if true and mKeyguardOccluded, request face auth listening, else default
-     *                to normal behavior.
-     *                See {@link KeyguardUpdateMonitor#shouldListenForFace()}
-     */
-    public void requestFaceAuthOnOccludingApp(boolean request) {
-        mOccludingAppRequestingFace = request;
-        int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP;
-        updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
-    }
-
-    /**
      * Request to listen for fingerprint when an app is occluding keyguard.
      *
      * @param request if true and mKeyguardOccluded, request fingerprint listening, else default
@@ -894,8 +792,7 @@
      */
     public void onCameraLaunched() {
         mSecureCameraLaunched = true;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_CAMERA_LAUNCHED);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
     /**
@@ -951,8 +848,7 @@
         // Don't send cancel if authentication succeeds
         mFingerprintCancelSignal = null;
         mLogger.logFingerprintSuccess(userId, isStrongBiometric);
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_FP_AUTHENTICATED);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1025,7 +921,6 @@
             mLogger.logFingerprintDetected(authUserId, isStrongBiometric);
         } else if (biometricSourceType == FACE) {
             mLogger.logFaceDetected(authUserId, isStrongBiometric);
-            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         }
 
         Trace.endSection();
@@ -1140,7 +1035,6 @@
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             }
-            stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT);
         }
 
         mLogger.logFingerprintError(msgId, errString);
@@ -1218,16 +1112,11 @@
     public void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
         Assert.isMainThread();
-        mUserFaceAuthenticated.put(userId,
-                new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, FACE);
         }
-        // Don't send cancel if authentication succeeds
-        mFaceCancelSignal = null;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         mLogger.d("onFaceAuthenticated");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1247,11 +1136,6 @@
         Trace.endSection();
     }
 
-    /**
-     * @deprecated This is being migrated to use modern architecture, this method is visible purely
-     * for bridging the gap while the migration is active.
-     */
-    @Deprecated
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
         String reason =
@@ -1264,8 +1148,6 @@
                 "faceFailure-" + reason);
 
         mLogger.d("onFaceAuthFailed");
-        mFaceCancelSignal = null;
-        setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1276,11 +1158,6 @@
                 mContext.getString(R.string.kg_face_not_recognized));
     }
 
-    /**
-     * @deprecated This is being migrated to use modern architecture, this method is visible purely
-     * for bridging the gap while the migration is active.
-     */
-    @Deprecated
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1298,44 +1175,23 @@
         }
     }
 
-    /**
-     * @deprecated This is being migrated to use modern architecture, this method is visible purely
-     * for bridging the gap while the migration is active.
-     */
-    @Deprecated
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
-        try {
-            if (mGoingToSleep) {
-                mLogger.d("Aborted successful auth because device is going to sleep.");
-                return;
-            }
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
-            if (userId != authUserId) {
-                mLogger.logFaceAuthForWrongUser(authUserId);
-                return;
-            }
-            if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
-                mLogger.logFaceAuthDisabledForUser(userId);
-                return;
-            }
-            mLogger.logFaceAuthSuccess(userId);
-            onFaceAuthenticated(userId, isStrongBiometric);
-        } finally {
-            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+        if (mGoingToSleep) {
+            mLogger.d("Aborted successful auth because device is going to sleep.");
+            return;
         }
+        final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+        if (userId != authUserId) {
+            mLogger.logFaceAuthForWrongUser(authUserId);
+            return;
+        }
+        mLogger.logFaceAuthSuccess(userId);
+        onFaceAuthenticated(userId, isStrongBiometric);
         Trace.endSection();
     }
 
-    /**
-     * @deprecated This is being migrated to use modern architecture, this method is visible purely
-     * for bridging the gap while the migration is active.
-     */
-    @Deprecated
     private void handleFaceHelp(int msgId, String helpString) {
-        if (mFaceAcquiredInfoIgnoreList.contains(msgId)) {
-            return;
-        }
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1345,49 +1201,19 @@
         }
     }
 
-    /**
-     * @deprecated This is being migrated to use modern architecture, this method is visible purely
-     * for bridging the gap while the migration is active.
-     */
-    @Deprecated
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
         mLogger.logFaceAuthError(msgId, originalErrMsg);
-        if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
-            mHandler.removeCallbacks(mFaceCancelNotReceived);
-        }
 
         // Error is always the end of authentication lifecycle
-        mFaceCancelSignal = null;
         boolean cameraPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
                 SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA);
 
-        if (msgId == FaceManager.FACE_ERROR_CANCELED
-                && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
-            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_DURING_CANCELLATION);
-        } else {
-            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
-        }
-
         final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE;
 
-        if (isHwUnavailable
-                || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
-            if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
-                mHardwareFaceUnavailableRetryCount++;
-                mHandler.removeCallbacks(mRetryFaceAuthentication);
-                mHandler.postDelayed(mRetryFaceAuthentication, HAL_ERROR_RETRY_TIMEOUT);
-            }
-        }
-
-        boolean lockedOutStateChanged = false;
         if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
-            lockedOutStateChanged = !mFaceLockedOutPermanent;
-            mFaceLockedOutPermanent = true;
-            if (isFaceClass3()) {
+            if (getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthStrong()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
             }
         }
@@ -1404,10 +1230,6 @@
             }
         }
 
-        if (lockedOutStateChanged) {
-            notifyLockedOutStateChanged(FACE);
-        }
-
         if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
@@ -1415,49 +1237,6 @@
         }
     }
 
-    private final Runnable mRetryFaceAuthentication = new Runnable() {
-        @Override
-        public void run() {
-            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
-        }
-    };
-
-    private void onFaceCancelNotReceived() {
-        mLogger.e("Face cancellation not received, transitioning to STOPPED");
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
-    }
-
-    private void handleFaceLockoutReset(@LockoutMode int mode) {
-        mLogger.logFaceLockoutReset(mode);
-        final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
-        mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
-        final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
-
-        mHandler.postDelayed(() -> updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
-
-        if (changed) {
-            notifyLockedOutStateChanged(FACE);
-        }
-    }
-
-    private void setFaceRunningState(int faceRunningState) {
-        boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
-        boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
-        mFaceRunningState = faceRunningState;
-        mLogger.logFaceRunningState(mFaceRunningState);
-        // Clients of KeyguardUpdateMonitor don't care about the internal state or about the
-        // asynchronousness of the cancel cycle. So only notify them if the actually running state
-        // has changed.
-        if (wasRunning != isRunning) {
-            notifyFaceRunningStateChanged();
-        }
-    }
-
     private void notifyFaceRunningStateChanged() {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1478,14 +1257,7 @@
      */
     @Deprecated
     public boolean isFaceDetectionRunning() {
-        if (isFaceAuthInteractorEnabled()) {
-            return getFaceAuthInteractor().isRunning();
-        }
-        return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
-    }
-
-    private boolean isFaceAuthInteractorEnabled() {
-        return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+        return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning();
     }
 
     private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
@@ -1495,14 +1267,32 @@
     /**
      * Set the face auth interactor that should be used for initiating face authentication.
      */
-    public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+    public void setFaceAuthInteractor(KeyguardFaceAuthInteractor faceAuthInteractor) {
+        if (mFaceAuthInteractor != null) {
+            mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+        }
         mFaceAuthInteractor = faceAuthInteractor;
         mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
     }
 
-    private FaceAuthenticationListener mFaceAuthenticationListener =
+    private final FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
                 @Override
+                public void onAuthEnrollmentStateChanged(boolean enrolled) {
+                    notifyAboutEnrollmentChange(TYPE_FACE);
+                }
+
+                @Override
+                public void onRunningStateChanged(boolean isRunning) {
+                    notifyFaceRunningStateChanged();
+                }
+
+                @Override
+                public void onLockoutStateChanged(boolean isLockedOut) {
+                    notifyLockedOutStateChanged(FACE);
+                }
+
+                @Override
                 public void onAuthenticationStatusChanged(
                         @NonNull FaceAuthenticationStatus status
                 ) {
@@ -1546,32 +1336,14 @@
     }
 
     /**
-     * @deprecated This method is not needed anymore with the new face auth system.
-     */
-    @Deprecated
-    private boolean isFaceDisabled(int userId) {
-        // TODO(b/140035044)
-        return whitelistIpcs(() ->
-                (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
-                        & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
-                || isSimPinSecure());
-    }
-
-    /**
      * @return whether the current user has been authenticated with face. This may be true
      * on the lockscreen if the user doesn't have bypass enabled.
      *
-     * @deprecated This is being migrated to use modern architecture.
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()}
      */
     @Deprecated
     public boolean getIsFaceAuthenticated() {
-        boolean faceAuthenticated = false;
-        BiometricAuthenticated bioFaceAuthenticated =
-                mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId());
-        if (bioFaceAuthenticated != null) {
-            faceAuthenticated = bioFaceAuthenticated.mAuthenticated;
-        }
-        return faceAuthenticated;
+        return getFaceAuthInteractor() != null && getFaceAuthInteractor().isAuthenticated();
     }
 
     public boolean getUserCanSkipBouncer(int userId) {
@@ -1590,17 +1362,19 @@
         BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
         boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
                 && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
-        return fingerprintAllowed || getUserUnlockedWithFace(userId);
+        boolean unlockedByFace = isCurrentUserUnlockedWithFace() && isUnlockingWithBiometricAllowed(
+                FACE);
+        return fingerprintAllowed || unlockedByFace;
     }
 
 
     /**
      * Returns whether the user is unlocked with face.
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()} instead
      */
-    public boolean getUserUnlockedWithFace(int userId) {
-        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
-        return face != null && face.mAuthenticated
-                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+    @Deprecated
+    public boolean isCurrentUserUnlockedWithFace() {
+        return getFaceAuthInteractor() != null && getFaceAuthInteractor().isAuthenticated();
     }
 
     /**
@@ -1609,13 +1383,11 @@
      */
     public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) {
         BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
-        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
         // fingerprint always bypasses
         boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
                 && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
-        boolean faceAllowed = face != null && face.mAuthenticated
-                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
-        return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass();
+        return fingerprintAllowed || (isCurrentUserUnlockedWithFace()
+                && mKeyguardBypassController.canBypass());
     }
 
     public boolean getUserTrustIsManaged(int userId) {
@@ -1684,10 +1456,22 @@
         // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
         // however the strong auth tracker does not include the temporary lockout
         // mFingerprintLockedOut.
+        if (!mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
+            return false;
+        }
+        boolean isFaceLockedOut =
+                getFaceAuthInteractor() != null && getFaceAuthInteractor().isLockedOut();
+        boolean isFaceAuthStrong =
+                getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthStrong();
+        boolean isFingerprintLockedOut = isFingerprintLockedOut();
+        boolean isAnyStrongBiometricLockedOut =
+                (isFingerprintClass3() && isFingerprintLockedOut) || (isFaceAuthStrong
+                        && isFaceLockedOut);
         // Class 3 biometric lockout will lockout ALL biometrics
-        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
-                && (!isFingerprintClass3() || !isFingerprintLockedOut())
-                && (!isFaceClass3() || !mFaceLockedOutPermanent);
+        if (isAnyStrongBiometricLockedOut) {
+            return false;
+        }
+        return !isFaceLockedOut || !isFingerprintLockedOut;
     }
 
     /**
@@ -1707,7 +1491,9 @@
             case FINGERPRINT:
                 return isUnlockingWithBiometricAllowed(isFingerprintClass3());
             case FACE:
-                return isUnlockingWithBiometricAllowed(isFaceClass3());
+                return getFaceAuthInteractor() != null
+                        && isUnlockingWithBiometricAllowed(
+                        getFaceAuthInteractor().isFaceAuthStrong());
             default:
                 return false;
         }
@@ -1757,14 +1543,9 @@
             }
         }
         if (userId == mSelectedUserInteractor.getSelectedUserId()) {
-            FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
-                    mStrongAuthTracker.getStrongAuthForUser(
-                            mSelectedUserInteractor.getSelectedUserId()));
-
             // Strong auth is only reset when primary auth is used to enter the device,
             // so we only check whether to stop biometric listening states here
-            updateBiometricListeningState(
-                    BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
         }
     }
 
@@ -1787,14 +1568,9 @@
             }
         }
         if (userId == mSelectedUserInteractor.getSelectedUserId()) {
-            FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
-                    mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
-                            mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1);
-
             // This is only reset when primary auth is used to enter the device, so we only check
             // whether to stop biometric listening states here
-            updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
-                    FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
         }
     }
 
@@ -1813,11 +1589,10 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         mLogger.logAssistantVisible(mAssistantVisible);
-        if (isFaceAuthInteractorEnabled()) {
-            mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+        if (getFaceAuthInteractor() != null) {
+            getFaceAuthInteractor().onAssistantTriggeredOnLockScreen();
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         if (mAssistantVisible) {
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
@@ -1929,14 +1704,6 @@
         }
     };
 
-    private final FaceManager.LockoutResetCallback mFaceLockoutResetCallback
-            = new FaceManager.LockoutResetCallback() {
-        @Override
-        public void onLockoutReset(int sensorId) {
-            handleFaceLockoutReset(BIOMETRIC_LOCKOUT_NONE);
-        }
-    };
-
     /**
      * Propagates a pointer down event to keyguard.
      */
@@ -1998,7 +1765,6 @@
                 @Override
                 public void onUdfpsPointerDown(int sensorId) {
                     mLogger.logUdfpsPointerDown(sensorId);
-                    requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
                 }
 
                 /**
@@ -2024,56 +1790,12 @@
                 }
             };
 
-    private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
-            = (sensorId, userId, isStrongBiometric) -> {
-                // Trigger the face detected path so the bouncer can be shown
-                handleBiometricDetected(userId, FACE, isStrongBiometric);
-            };
-
-    @VisibleForTesting
-    final FaceManager.AuthenticationCallback mFaceAuthenticationCallback
-            = new FaceManager.AuthenticationCallback() {
-
-                @Override
-                public void onAuthenticationFailed() {
-                    handleFaceAuthFailed();
-                }
-
-                @Override
-                public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
-                    handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
-                }
-
-                @Override
-                public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
-                    handleFaceHelp(helpMsgId, helpString.toString());
-                }
-
-                @Override
-                public void onAuthenticationError(int errMsgId, CharSequence errString) {
-                    handleFaceError(errMsgId, errString.toString());
-                }
-
-                @Override
-                public void onAuthenticationAcquired(int acquireInfo) {
-                    handleFaceAcquired(acquireInfo);
-                }
-    };
-
     @VisibleForTesting
     final DevicePostureController.Callback mPostureCallback =
             new DevicePostureController.Callback() {
                 @Override
                 public void onPostureChanged(@DevicePostureInt int posture) {
-                    boolean currentPostureAllowsFaceAuth = doesPostureAllowFaceAuth(mPostureState);
-                    boolean newPostureAllowsFaceAuth = doesPostureAllowFaceAuth(posture);
-                    mPostureState = posture;
-                    if (currentPostureAllowsFaceAuth && !newPostureAllowsFaceAuth) {
-                        mLogger.d("New posture does not allow face auth, stopping it");
-                        updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                                FACE_AUTH_UPDATED_POSTURE_CHANGED);
-                    }
-                    if (mPostureState == DEVICE_POSTURE_OPENED) {
+                    if (posture == DEVICE_POSTURE_OPENED) {
                         mLogger.d("Posture changed to open - attempting to request active unlock");
                         requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE,
                                 false);
@@ -2083,14 +1805,10 @@
 
     @VisibleForTesting
     CancellationSignal mFingerprintCancelSignal;
-    @VisibleForTesting
-    CancellationSignal mFaceCancelSignal;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties =
             Collections.emptyList();
-    private List<FaceSensorPropertiesInternal> mFaceSensorProperties = Collections.emptyList();
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
-    private boolean mFaceLockedOutPermanent;
 
     /**
      * When we receive a {@link android.content.Intent#ACTION_SIM_STATE_CHANGED} broadcast,
@@ -2229,15 +1947,7 @@
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         Assert.isMainThread();
 
-        mAllowedDisplayStateWhileAwakeForFaceAuth = true;
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-        if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
-            FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_STARTED_WAKING_UP);
-        } else {
-            mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
-        }
         requestActiveUnlockFromWakeReason(pmWakeReason, true);
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2264,7 +1974,7 @@
         // which results in face auth running once on AoD.
         mAssistantVisible = false;
         mLogger.d("Started going to sleep, mGoingToSleep=true, mAssistantVisible=false");
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
     protected void handleFinishedGoingToSleep(int arg1) {
@@ -2276,15 +1986,12 @@
                 cb.onFinishedGoingToSleep(arg1);
             }
         }
-        updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP);
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
     private void handleScreenTurnedOff() {
         Assert.isMainThread();
         mHardwareFingerprintUnavailableRetryCount = 0;
-        mHardwareFaceUnavailableRetryCount = 0;
     }
 
     private void handleDreamingStateChanged(int dreamStart) {
@@ -2297,9 +2004,6 @@
             }
         }
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-        if (mIsDreaming) {
-            updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED);
-        }
     }
 
     private void handleUserUnlocked(int userId) {
@@ -2344,7 +2048,6 @@
     @VisibleForTesting
     void resetBiometricListeningState() {
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     }
 
     @VisibleForTesting
@@ -2376,17 +2079,14 @@
             SensorPrivacyManager sensorPrivacyManager,
             TelephonyManager telephonyManager,
             PackageManager packageManager,
-            @Nullable FaceManager faceManager,
             @Nullable FingerprintManager fingerprintManager,
             @Nullable BiometricManager biometricManager,
             FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
             DevicePostureController devicePostureController,
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
             TaskStackChangeListeners taskStackChangeListeners,
-            IActivityTaskManager activityTaskManagerService,
-            DisplayTracker displayTracker,
-            WakefulnessLifecycle wakefulness,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            IActivityTaskManager activityTaskManagerService) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2413,16 +2113,9 @@
         mDreamManager = dreamManager;
         mTelephonyManager = telephonyManager;
         mDevicePolicyManager = devicePolicyManager;
-        mPostureController = devicePostureController;
         mPackageManager = packageManager;
         mFpm = fingerprintManager;
-        mFaceManager = faceManager;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
-        mFaceAcquiredInfoIgnoreList = Arrays.stream(
-                mContext.getResources().getIntArray(
-                        R.array.config_face_acquire_device_entry_ignorelist))
-                .boxed()
-                .collect(Collectors.toSet());
         mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
                 R.integer.config_face_auth_supported_posture);
         mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
@@ -2432,9 +2125,6 @@
                 .collect(Collectors.toSet());
         mTaskStackChangeListeners = taskStackChangeListeners;
         mActivityTaskManager = activityTaskManagerService;
-        mWakefulness = wakefulness;
-        mDisplayTracker = displayTracker;
-        mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
         mSelectedUserInteractor = selectedUserInteractor;
 
         mHandler = new Handler(mainLooper) {
@@ -2521,8 +2211,7 @@
                         setAssistantVisible((boolean) msg.obj);
                         break;
                     case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
-                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                                FACE_AUTH_UPDATED_FP_AUTHENTICATED);
+                        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
                         break;
                     case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
                         updateLogoutEnabled();
@@ -2617,18 +2306,6 @@
                     });
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
-        if (mFaceManager != null) {
-            mFaceManager.addAuthenticatorsRegisteredCallback(
-                    new IFaceAuthenticatorsRegisteredCallback.Stub() {
-                        @Override
-                        public void onAllAuthenticatorsRegistered(
-                                List<FaceSensorPropertiesInternal> sensors) throws RemoteException {
-                            mFaceSensorProperties = sensors;
-                            mLogger.d("FaceManager onAllAuthenticatorsRegistered");
-                        }
-                    });
-            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
-        }
 
         if (biometricManager != null) {
             biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
@@ -2639,16 +2316,16 @@
             @Override
             public void onAllAuthenticatorsRegistered(
                     @BiometricAuthenticator.Modality int modality) {
-                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                        FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED));
+                mainExecutor.execute(
+                        () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
             @Override
             public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
                 mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0)
                         .sendToTarget();
-                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                        FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED));
+                mainExecutor.execute(
+                        () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
             @Override
@@ -2665,9 +2342,9 @@
             }
         });
         if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
-            mPostureController.addCallback(mPostureCallback);
+            devicePostureController.addCallback(mPostureCallback);
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
 
         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
         mIsSystemUser = mUserManager.isSystemUser();
@@ -2721,10 +2398,6 @@
         }
     }
 
-    private boolean isFaceSupported() {
-        return mFaceManager != null && !mFaceSensorProperties.isEmpty();
-    }
-
     private boolean isFingerprintSupported() {
         return mFpm != null && !mFingerprintSensorProperties.isEmpty();
     }
@@ -2760,17 +2433,13 @@
     }
 
     /**
-     * @return true if there's at least one face enrolled for the given user.
-     */
-    public boolean isFaceEnrolled(int userId) {
-        return mAuthController.isFaceAuthEnrolled(userId);
-    }
-
-    /**
      * @return true if there's at least one face enrolled
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
      */
-    public boolean isFaceEnrolled() {
-        return isFaceEnrolled(mSelectedUserInteractor.getSelectedUserId());
+    @Deprecated
+    public boolean isFaceEnabledAndEnrolled() {
+        return getFaceAuthInteractor() != null
+                && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
     }
 
     private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
@@ -2798,12 +2467,6 @@
         mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
     }
 
-    private void updateBiometricListeningState(int action,
-            @NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        updateFingerprintListeningState(action);
-        updateFaceListeningState(action, faceAuthUiEvent);
-    }
-
     private void updateFingerprintListeningState(int action) {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
@@ -2859,57 +2522,9 @@
             return;
         }
         mAuthInterruptActive = active;
-        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD);
         requestActiveUnlock(ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE, "onReach");
     }
 
-    /**
-     * Requests face authentication if we're on a state where it's allowed.
-     * This will re-trigger auth in case it fails.
-     * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
-     * invoked.
-     * @return current face auth detection state, true if it is running.
-     * @deprecated This is being migrated to use modern architecture.
-     */
-    @Deprecated
-    public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
-        mLogger.logFaceAuthRequested(reason);
-        updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
-        return isFaceDetectionRunning();
-    }
-
-    /**
-     * In case face auth is running, cancel it.
-     */
-    public void cancelFaceAuth() {
-        stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER);
-    }
-
-    private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        if (isFaceAuthInteractorEnabled()) return;
-        // If this message exists, we should not authenticate again until this message is
-        // consumed by the handler
-        if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
-            return;
-        }
-        mHandler.removeCallbacks(mRetryFaceAuthentication);
-        boolean shouldListenForFace = shouldListenForFace();
-        if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
-            if (action == BIOMETRIC_ACTION_START) {
-                mLogger.v("Ignoring stopListeningForFace()");
-                return;
-            }
-            stopListeningForFace(faceAuthUiEvent);
-        } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
-            if (action == BIOMETRIC_ACTION_STOP) {
-                mLogger.v("Ignoring startListeningForFace()");
-                return;
-            }
-            startListeningForFace(faceAuthUiEvent);
-        }
-    }
-
     @Nullable
     private InstanceId getKeyguardSessionId() {
         return mSessionTrackerProvider.get().getSessionId(SESSION_KEYGUARD);
@@ -2994,8 +2609,9 @@
             @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
             String extraReason
     ) {
-        final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
-                && mKeyguardBypassController.canBypass();
+        final boolean canFaceBypass =
+                isFaceEnabledAndEnrolled() && mKeyguardBypassController != null
+                        && mKeyguardBypassController.canBypass();
         requestActiveUnlock(
                 requestOrigin,
                 extraReason, canFaceBypass
@@ -3022,8 +2638,6 @@
     public void setAlternateBouncerShowing(boolean showing) {
         mAlternateBouncerShowing = showing;
         if (mAlternateBouncerShowing) {
-            updateFaceListeningState(BIOMETRIC_ACTION_START,
-                    FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN);
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                     "alternateBouncer");
@@ -3101,17 +2715,6 @@
                         mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
-    private boolean shouldListenForFaceAssistant() {
-        BiometricAuthenticated face = mUserFaceAuthenticated.get(
-                mSelectedUserInteractor.getSelectedUserId());
-        return mAssistantVisible
-                // There can be intermediate states where mKeyguardShowing is false but
-                // mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
-                && (mKeyguardShowing && mKeyguardOccluded)
-                && !(face != null && face.mAuthenticated)
-                && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
-    }
-
     private boolean shouldTriggerActiveUnlockForAssistant() {
         return mAssistantVisible && mKeyguardOccluded
                 && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
@@ -3195,107 +2798,14 @@
 
     /**
      * If face auth is allows to scan on this exact moment.
+     *
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#canFaceAuthRun()}
      */
+    @Deprecated
     public boolean shouldListenForFace() {
-        if (mFaceManager == null) {
-            // Device does not have face auth
-            return false;
-        }
-
-        if (isFaceAuthInteractorEnabled()) {
-            return mFaceAuthInteractor.canFaceAuthRun();
-        }
-
-        final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
-        final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
-                && !statusBarShadeLocked;
-        final int user = mSelectedUserInteractor.getSelectedUserId();
-        final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
-        final boolean canBypass = mKeyguardBypassController != null
-                && mKeyguardBypassController.canBypass();
-        // There's no reason to ask the HAL for authentication when the user can dismiss the
-        // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
-        // the lock screen even when TrustAgents are keeping the device unlocked.
-        final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
-
-        // If the device supports face detection (without authentication), if bypass is enabled,
-        // allow face detection to happen even if stronger auth is required. When face is detected,
-        // we show the bouncer. However, if the user manually locked down the device themselves,
-        // never attempt to detect face.
-        final boolean supportsDetect = isFaceSupported()
-                && mFaceSensorProperties.get(0).supportsFaceDetection
-                && canBypass && !mPrimaryBouncerIsOrWillBeShowing
-                && !isUserInLockdown(user);
-        final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
-
-        // If the face or fp has recently been authenticated do not attempt to authenticate again.
-        final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
-        final boolean faceDisabledForUser = isFaceDisabled(user);
-        final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
-        final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
-        final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
-        final boolean isPostureAllowedForFaceAuth = doesPostureAllowFaceAuth(mPostureState);
-        // Only listen if this KeyguardUpdateMonitor belongs to the system user. There is an
-        // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
-        final boolean shouldListen =
-                (mPrimaryBouncerFullyShown
-                        || mAuthInterruptActive
-                        || mOccludingAppRequestingFace
-                        || awakeKeyguard
-                        || shouldListenForFaceAssistant
-                        || isUdfpsFingerDown
-                        || mAlternateBouncerShowing)
-                && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
-                && !mKeyguardGoingAway && biometricEnabledForUser
-                && faceAuthAllowedOrDetectionIsNeeded && mIsSystemUser
-                && (!mSecureCameraLaunched || mAlternateBouncerShowing)
-                && faceAndFpNotAuthenticated
-                && !mGoingToSleep
-                && isPostureAllowedForFaceAuth
-                && mAllowedDisplayStateWhileAwakeForFaceAuth;
-
-        // Aggregate relevant fields for debug logging.
-        logListenerModelData(
-                new KeyguardFaceListenModel(
-                    System.currentTimeMillis(),
-                    user,
-                    shouldListen,
-                    mAllowedDisplayStateWhileAwakeForFaceAuth,
-                    mAlternateBouncerShowing,
-                    mAuthInterruptActive,
-                    biometricEnabledForUser,
-                    mPrimaryBouncerFullyShown,
-                    faceAndFpNotAuthenticated,
-                    faceAuthAllowed,
-                    faceDisabledForUser,
-                    isFaceLockedOut(),
-                    mGoingToSleep,
-                    awakeKeyguard,
-                    mKeyguardGoingAway,
-                    shouldListenForFaceAssistant,
-                    mOccludingAppRequestingFace,
-                    isPostureAllowedForFaceAuth,
-                    mSecureCameraLaunched,
-                    supportsDetect,
-                    mSwitchingUser,
-                    mIsSystemUser,
-                    isUdfpsFingerDown,
-                    userNotTrustedOrDetectionIsNeeded));
-
-        return shouldListen;
+        return getFaceAuthInteractor() != null && getFaceAuthInteractor().canFaceAuthRun();
     }
 
-    private boolean doesPostureAllowFaceAuth(@DevicePostureInt int posture) {
-        return mConfigFaceAuthSupportedPosture == DEVICE_POSTURE_UNKNOWN
-                || (posture == mConfigFaceAuthSupportedPosture);
-    }
-
-    /**
-     * If the current device posture allows face auth to run.
-     */
-    public boolean doesCurrentPostureAllowFaceAuth() {
-        return doesPostureAllowFaceAuth(mPostureState);
-    }
 
     private void logListenerModelData(@NonNull KeyguardListenModel model) {
         mLogger.logKeyguardListenerModel(model);
@@ -3303,8 +2813,6 @@
             mFingerprintListenBuffer.insert((KeyguardFingerprintListenModel) model);
         } else if (model instanceof KeyguardActiveUnlockModel) {
             mActiveUnlockTriggerBuffer.insert((KeyguardActiveUnlockModel) model);
-        } else if (model instanceof KeyguardFaceListenModel) {
-            mFaceListenBuffer.insert((KeyguardFaceListenModel) model);
         }
     }
 
@@ -3355,85 +2863,16 @@
         }
     }
 
-    private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        final int userId = mSelectedUserInteractor.getSelectedUserId();
-        final boolean unlockPossible = isUnlockWithFacePossible(userId);
-        if (mFaceCancelSignal != null) {
-            mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
-        }
-
-        if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
-            setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
-            return;
-        } else if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
-            // Waiting for ERROR_CANCELED before requesting auth again
-            return;
-        }
-        mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent);
-        mUiEventLogger.logWithInstanceIdAndPosition(
-                faceAuthUiEvent,
-                0,
-                null,
-                getKeyguardSessionId(),
-                faceAuthUiEvent.getExtraInfo()
-        );
-        mLogger.logFaceUnlockPossible(unlockPossible);
-        if (unlockPossible) {
-            mFaceCancelSignal = new CancellationSignal();
-
-            final FaceAuthenticateOptions faceAuthenticateOptions =
-                    new SysUiFaceAuthenticateOptions(
-                            userId,
-                            faceAuthUiEvent,
-                            faceAuthUiEvent == FACE_AUTH_UPDATED_STARTED_WAKING_UP
-                                    ? faceAuthUiEvent.getExtraInfo()
-                                    : WAKE_REASON_UNKNOWN
-                    ).toFaceAuthenticateOptions();
-            // This would need to be updated for multi-sensor devices
-            final boolean supportsFaceDetection = isFaceSupported()
-                    && mFaceSensorProperties.get(0).supportsFaceDetection;
-            if (!isUnlockingWithBiometricAllowed(FACE)) {
-                final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
-                        && isFingerprintDetectionRunning();
-                if (supportsFaceDetection && !udfpsFingerprintAuthRunning) {
-                    // Run face detection. (If a face is detected, show the bouncer.)
-                    mLogger.v("startListeningForFace - detect");
-                    mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback,
-                            faceAuthenticateOptions);
-                } else {
-                    // Don't run face detection. Instead, inform the user
-                    // face auth is unavailable and how to proceed.
-                    // (ie: "Use fingerprint instead" or "Swipe up to open")
-                    mLogger.v("Ignoring \"startListeningForFace - detect\". "
-                            + "Informing user face isn't available.");
-                    mFaceAuthenticationCallback.onAuthenticationHelp(
-                            BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
-                            mContext.getResources().getString(
-                                    R.string.keyguard_face_unlock_unavailable)
-                    );
-                    return;
-                }
-            } else {
-                mLogger.v("startListeningForFace - authenticate");
-                final boolean isBypassEnabled = mKeyguardBypassController != null
-                        && mKeyguardBypassController.isBypassEnabled();
-                mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
-                        mFaceAuthenticationCallback, null /* handler */,
-                        faceAuthenticateOptions);
-            }
-            setFaceRunningState(BIOMETRIC_STATE_RUNNING);
-        }
-    }
-
     public boolean isFingerprintLockedOut() {
         return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
     }
 
+    /**
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#isLockedOut()}
+     */
+    @Deprecated
     public boolean isFaceLockedOut() {
-        if (isFaceAuthInteractorEnabled()) {
-            return getFaceAuthInteractor().isLockedOut();
-        }
-        return mFaceLockedOutPermanent;
+        return getFaceAuthInteractor() != null && getFaceAuthInteractor().isLockedOut();
     }
 
     /**
@@ -3444,7 +2883,7 @@
      * @return {@code true} if possible.
      */
     public boolean isUnlockingWithBiometricsPossible(int userId) {
-        return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
+        return isUnlockWithFacePossible() || isUnlockWithFingerprintPossible(userId);
     }
 
     /**
@@ -3455,8 +2894,12 @@
      * @return {@code true} if possible.
      */
     public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
-        return (!isFaceClass3() && isUnlockWithFacePossible(userId))
-                || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId));
+        if (getFaceAuthInteractor() != null && !getFaceAuthInteractor().isFaceAuthStrong()) {
+            if (isUnlockWithFacePossible()) {
+                return true;
+            }
+        }
+        return isFingerprintClass3() && isUnlockWithFingerprintPossible(userId);
     }
 
     @SuppressLint("MissingPermission")
@@ -3466,16 +2909,13 @@
     }
 
     /**
-     * @deprecated This is being migrated to use modern architecture.
+     * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
      */
     @VisibleForTesting
     @Deprecated
-    public boolean isUnlockWithFacePossible(int userId) {
-        if (isFaceAuthInteractorEnabled()) {
-            return getFaceAuthInteractor() != null
+    public boolean isUnlockWithFacePossible() {
+        return getFaceAuthInteractor() != null
                     && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
-        }
-        return isFaceSupported() && isFaceEnrolled(userId) && !isFaceDisabled(userId);
     }
 
     private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) {
@@ -3513,25 +2953,6 @@
         }
     }
 
-    private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        if (isFaceAuthInteractorEnabled()) return;
-        mLogger.v("stopListeningForFace()");
-        mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
-        mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
-        if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
-            if (mFaceCancelSignal != null) {
-                mFaceCancelSignal.cancel();
-                mFaceCancelSignal = null;
-                mHandler.removeCallbacks(mFaceCancelNotReceived);
-                mHandler.postDelayed(mFaceCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT);
-            }
-            setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
-        }
-        if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
-            setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
-        }
-    }
-
     private boolean isDeviceProvisionedInSettingsDb() {
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
@@ -3617,13 +3038,6 @@
             }
         }
 
-        // Immediately stop previous biometric listening states.
-        // Resetting lockout states updates the biometric listening states.
-        if (isFaceSupported()) {
-            stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
-            handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
-                    mFaceSensorProperties.get(0).sensorId, userId));
-        }
         if (isFingerprintSupported()) {
             stopListeningForFingerprint();
             handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
@@ -3866,8 +3280,7 @@
     @VisibleForTesting
     protected void handleKeyguardReset() {
         mLogger.d("handleKeyguardReset");
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_KEYGUARD_RESET);
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
     }
 
@@ -3935,8 +3348,6 @@
                     cb.onKeyguardBouncerFullyShowingChanged(mPrimaryBouncerFullyShown);
                 }
             }
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN);
         }
     }
 
@@ -4071,8 +3482,7 @@
         }
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post it first
-        mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_UPDATED_USER_SWITCHING));
+        mHandler.post(() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
     }
 
     private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -4161,7 +3571,6 @@
     private void clearBiometricRecognized(int unlockedUser) {
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
-        mUserFaceAuthenticated.clear();
         mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
         mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
         mLogger.d("clearBiometricRecognized");
@@ -4394,12 +3803,6 @@
         return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0));
     }
 
-    @VisibleForTesting
-    protected boolean isFaceClass3() {
-        // This assumes that there is at most one face sensor property
-        return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0));
-    }
-
     private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) {
         return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG;
     }
@@ -4411,8 +3814,8 @@
         mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
         mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
-        if (isFaceAuthInteractorEnabled()) {
-            mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+        if (getFaceAuthInteractor() != null) {
+            getFaceAuthInteractor().unregisterListener(mFaceAuthenticationListener);
         }
 
         if (mDeviceProvisionedObserver != null) {
@@ -4432,7 +3835,6 @@
 
         mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
         mTrustManager.unregisterTrustListener(this);
-        mDisplayTracker.removeCallback(mDisplayCallback);
 
         mHandler.removeCallbacksAndMessages(null);
     }
@@ -4446,7 +3848,6 @@
                 mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
-        pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
@@ -4519,50 +3920,11 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         }
-        if (isFaceSupported()) {
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
-            final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
-            BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
-            pw.println("  Face authentication state (user=" + userId + ")");
-            pw.println("    isFaceClass3=" + isFaceClass3());
-            pw.println("    allowed="
-                    + (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric)));
-            pw.println("    auth'd="
-                    + (face != null && face.mAuthenticated));
-            pw.println("    authSinceBoot="
-                    + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
-            pw.println("    disabled(DPM)=" + isFaceDisabled(userId));
-            pw.println("    possible=" + isUnlockWithFacePossible(userId));
-            pw.println("    listening: actual=" + mFaceRunningState
-                    + " expected=(" + (shouldListenForFace() ? 1 : 0));
-            pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
-            pw.println("    isNonStrongBiometricAllowedAfterIdleTimeout="
-                    + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(userId));
-            pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
-            pw.println("    mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
-            pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
-            pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
-            pw.println("    mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown);
-            pw.println("    mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition);
-            new DumpsysTableLogger(
-                    "KeyguardFaceListen",
-                    KeyguardFaceListenModel.TABLE_HEADERS,
-                    mFaceListenBuffer.toList()
-            ).printTableData(pw);
-        } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
-            pw.println("  Face state (user=" + userId + ")");
-            pw.println("    mFaceSensorProperties.isEmpty="
-                    + mFaceSensorProperties.isEmpty());
-            pw.println("    mFaceManager.isHardwareDetected="
-                    + mFaceManager.isHardwareDetected());
-
-            new DumpsysTableLogger(
-                    "KeyguardFaceListen",
-                    KeyguardFingerprintListenModel.TABLE_HEADERS,
-                    mFingerprintListenBuffer.toList()
-            ).printTableData(pw);
-        }
+        final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+        final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+        pw.println("    authSinceBoot="
+                + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
+        pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
         pw.println("ActiveUnlockRunning="
                 + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
         new DumpsysTableLogger(
@@ -4577,13 +3939,8 @@
      * Cancels all operations in the scheduler if it is hung for 10 seconds.
      */
     public void startBiometricWatchdog() {
-        final boolean isFaceAuthInteractorEnabled = isFaceAuthInteractorEnabled();
         mBackgroundExecutor.execute(() -> {
             Trace.beginSection("#startBiometricWatchdog");
-            if (mFaceManager != null && !isFaceAuthInteractorEnabled) {
-                mLogger.scheduleWatchdog("face");
-                mFaceManager.scheduleWatchdog();
-            }
             if (mFpm != null) {
                 mLogger.scheduleWatchdog("fingerprint");
                 mFpm.scheduleWatchdog();
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 07359d1..175fcdb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -26,7 +26,7 @@
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.annotation.SuppressLint;
@@ -395,7 +395,7 @@
             mView.updateIcon(ICON_LOCK, true);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
-        } else if (mIsDozing && mFeatureFlags.isEnabled(NEW_AOD_TRANSITION)) {
+        } else if (mIsDozing && newAodTransition()) {
             mView.animate()
                     .alpha(0f)
                     .setDuration(FADE_OUT_DURATION_MS)
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 5bf8d63..055ca56 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -20,14 +20,12 @@
 import android.hardware.biometrics.BiometricConstants.LockoutMode
 import android.hardware.biometrics.BiometricSourceType
 import android.os.PowerManager
-import android.os.PowerManager.WakeReason
 import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager
 import com.android.keyguard.ActiveUnlockConfig
-import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardListenModel
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.TrustGrantFlags
@@ -102,14 +100,6 @@
         logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
     }
 
-    fun logFaceAuthDisabledForUser(userId: Int) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { int1 = userId },
-            { "Face authentication disabled by DPM for userId: $int1" }
-        )
-    }
     fun logFaceAuthError(msgId: Int, originalErrMsg: String) {
         logBuffer.log(
             TAG,
@@ -131,31 +121,10 @@
         )
     }
 
-    fun logFaceAuthRequested(reason: String?) {
-        logBuffer.log(TAG, DEBUG, { str1 = reason }, { "requestFaceAuth() reason=$str1" })
-    }
-
     fun logFaceAuthSuccess(userId: Int) {
         logBuffer.log(TAG, DEBUG, { int1 = userId }, { "Face auth succeeded for user $int1" })
     }
 
-    fun logFaceLockoutReset(@LockoutMode mode: Int) {
-        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" })
-    }
-
-    fun logFaceRunningState(faceRunningState: Int) {
-        logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
-    }
-
-    fun logFaceUnlockPossible(isFaceUnlockPossible: Boolean) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { bool1 = isFaceUnlockPossible },
-            { "isUnlockWithFacePossible: $bool1" }
-        )
-    }
-
     fun logFingerprintAuthForWrongUser(authUserId: Int) {
         logBuffer.log(
             FP_LOG_TAG,
@@ -301,15 +270,6 @@
         logBuffer.log(TAG, VERBOSE, { str1 = "$callback" }, { "*** register callback for $str1" })
     }
 
-    fun logRetryingAfterFaceHwUnavailable(retryCount: Int) {
-        logBuffer.log(
-            TAG,
-            WARNING,
-            { int1 = retryCount },
-            { "Retrying face after HW unavailable, attempt $int1" }
-        )
-    }
-
     fun logRetryAfterFpErrorWithDelay(msgId: Int, errString: String?, delay: Int) {
         logBuffer.log(
             TAG,
@@ -419,43 +379,6 @@
         logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
     }
 
-    fun logStartedListeningForFace(faceRunningState: Int, faceAuthUiEvent: FaceAuthUiEvent) {
-        logBuffer.log(
-            TAG,
-            VERBOSE,
-            {
-                int1 = faceRunningState
-                str1 = faceAuthUiEvent.reason
-                str2 = faceAuthUiEvent.extraInfoToString()
-            },
-            { "startListeningForFace(): $int1, reason: $str1 $str2" }
-        )
-    }
-
-    fun logStartedListeningForFaceFromWakeUp(faceRunningState: Int, @WakeReason pmWakeReason: Int) {
-        logBuffer.log(
-            TAG,
-            VERBOSE,
-            {
-                int1 = faceRunningState
-                str1 = PowerManager.wakeReasonToString(pmWakeReason)
-            },
-            { "startListeningForFace(): $int1, reason: wakeUp-$str1" }
-        )
-    }
-
-    fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
-        logBuffer.log(
-            TAG,
-            VERBOSE,
-            {
-                int1 = faceRunningState
-                str1 = faceAuthReason
-            },
-            { "stopListeningForFace(): currentFaceRunningState: $int1, reason: $str1" }
-        )
-    }
-
     fun logSubInfo(subInfo: SubscriptionInfo?) {
         logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" })
     }
@@ -476,22 +399,6 @@
         logBuffer.log(TAG, DEBUG, { int1 = sensorId }, { "onUdfpsPointerUp, sensorId: $int1" })
     }
 
-    fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) {
-        logBuffer.log(
-            TAG,
-            ERROR,
-            {
-                int1 = faceRunningState
-                bool1 = unlockPossible
-            },
-            {
-                "Cancellation signal is not null, high chance of bug in " +
-                    "face auth lifecycle management. " +
-                    "Face state: $int1, unlockPossible: $bool1"
-            }
-        )
-    }
-
     fun logUnexpectedFpCancellationSignalState(
         fingerprintRunningState: Int,
         unlockPossible: Boolean
@@ -588,15 +495,6 @@
         )
     }
 
-    fun logSkipUpdateFaceListeningOnWakeup(@WakeReason pmWakeReason: Int) {
-        logBuffer.log(
-            TAG,
-            VERBOSE,
-            { str1 = PowerManager.wakeReasonToString(pmWakeReason) },
-            { "Skip updating face listening state on wakeup from $str1" }
-        )
-    }
-
     fun logTaskStackChangedForAssistant(assistantVisible: Boolean) {
         logBuffer.log(
             TAG,
@@ -648,18 +546,6 @@
         )
     }
 
-    fun logFaceEnrolledUpdated(oldValue: Boolean, newValue: Boolean) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            {
-                bool1 = oldValue
-                bool2 = newValue
-            },
-            { "Face enrolled state changed: old: $bool1, new: $bool2" }
-        )
-    }
-
     fun logTrustUsuallyManagedUpdated(
         userId: Int,
         oldValue: Boolean,
@@ -745,18 +631,6 @@
         )
     }
 
-    fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
-        logBuffer.log(
-            FP_LOG_TAG,
-            DEBUG,
-            {
-                int1 = helpMsgId
-                str1 = "$helpString"
-            },
-            { "fingerprint help message: $int1, $str1" }
-        )
-    }
-
     fun logFingerprintAcquired(acquireInfo: Int) {
         logBuffer.log(
             FP_LOG_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 9305ab6..7ccf704 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -7,6 +7,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.SearchManager;
+import android.app.StatusBarManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -439,6 +440,14 @@
                     public void onStartPerceiving() {
                         mAssistUtils.enableVisualQueryDetection(
                                 mVisualQueryDetectionAttentionListener);
+                        final StatusBarManager statusBarManager =
+                                mContext.getSystemService(StatusBarManager.class);
+                        if (statusBarManager != null) {
+                            statusBarManager.setIcon("assist_attention",
+                                    R.drawable.ic_assistant_attention_indicator,
+                                    0, "Attention Icon for Assistant");
+                            statusBarManager.setIconVisibility("assist_attention", false);
+                        }
                     }
 
                     @Override
@@ -447,11 +456,20 @@
                         // accordingly).
                         handleVisualAttentionChanged(false);
                         mAssistUtils.disableVisualQueryDetection();
+                        final StatusBarManager statusBarManager =
+                                mContext.getSystemService(StatusBarManager.class);
+                        if (statusBarManager != null) {
+                            statusBarManager.removeIcon("assist_attention");
+                        }
                     }
                 });
     }
 
     private void handleVisualAttentionChanged(boolean attentionGained) {
+        final StatusBarManager statusBarManager = mContext.getSystemService(StatusBarManager.class);
+        if (statusBarManager != null) {
+            statusBarManager.setIconVisibility("assist_attention", attentionGained);
+        }
         mVisualQueryAttentionListeners.forEach(
                 attentionGained
                         ? VisualQueryAttentionListener::onAttentionGained
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 1ac4163..ab23564 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -30,6 +30,7 @@
 import android.graphics.PixelFormat;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -168,7 +169,7 @@
     // HAT received from LockSettingsService when credential is verified.
     @Nullable private byte[] mCredentialAttestation;
 
-    // TODO(b/287311775): remove when legacy prompt is replaced
+    // TODO(b/313469218): remove when legacy prompt is replaced
     @Deprecated
     static class Config {
         Context mContext;
@@ -220,6 +221,9 @@
             mHandler.postDelayed(() -> {
                 addCredentialView(false /* animatePanel */, true /* animateContents */);
             }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS);
+
+            // TODO(b/313469218): Remove Config
+            mConfig.mPromptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57e252d..8fe42b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -100,7 +100,6 @@
 import javax.inject.Provider;
 
 import kotlin.Unit;
-
 import kotlinx.coroutines.CoroutineScope;
 
 /**
@@ -1099,6 +1098,7 @@
         // TODO(b/141025588): Create separate methods for handling hard and soft errors.
         final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
                 || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
+                || error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL
                 || isCameraPrivacyEnabled);
         if (mCurrentDialog != null) {
             if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index f94f8c5..6345312 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -31,11 +31,11 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.settingslib.Utils
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.res.R
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -191,7 +191,7 @@
 
         // This code path is not used if the KeyguardTransitionRepository is managing the light
         // reveal scrim.
-        if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+        if (!lightRevealMigration()) {
             if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
                 circleReveal?.let {
                     lightRevealScrim.revealAmount = 0f
@@ -210,7 +210,7 @@
     }
 
     override fun onKeyguardFadingAwayChanged() {
-        if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+        if (lightRevealMigration()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index bbdcadb..cb75049 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -20,12 +20,10 @@
 import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
-import com.android.keyguard.FaceAuthApiRequestReason
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.res.R
 import javax.inject.Inject
 
 /**
@@ -37,12 +35,11 @@
 @Inject
 constructor(
     @Main private val resources: Resources,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val faceAuthInteractor: KeyguardFaceAuthInteractor,
 ) : View.AccessibilityDelegate() {
     override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
-        if (keyguardUpdateMonitor.shouldListenForFace()) {
+        if (faceAuthInteractor.canFaceAuthRun()) {
             val clickActionToRetryFace =
                 AccessibilityNodeInfo.AccessibilityAction(
                     AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
@@ -54,7 +51,6 @@
 
     override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
-            keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
             faceAuthInteractor.onAccessibilityAction()
             true
         } else super.performAccessibilityAction(host, action, args)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 8f61dbf..91cee9e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.res.R
 import com.android.systemui.util.boundsOnScreen
@@ -190,7 +191,10 @@
     }
 
     private fun listenForAlternateBouncerVisibility() {
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController")
+        if (!DeviceEntryUdfpsRefactor.isEnabled) {
+            alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController")
+        }
+
         scope.launch {
             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
                 if (isVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b064391..72d14ba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -52,6 +52,7 @@
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -63,7 +64,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -78,9 +78,9 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -102,8 +102,6 @@
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.time.SystemClock;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -113,6 +111,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
+
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
@@ -150,7 +150,6 @@
     @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
     @NonNull private final VibratorHelper mVibrator;
-    @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
     @NonNull private final AccessibilityManager mAccessibilityManager;
@@ -281,7 +280,6 @@
                             fromUdfpsView
                         ),
                         mActivityLaunchAnimator,
-                        mFeatureFlags,
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
                         mUdfpsKeyguardAccessibilityDelegate,
@@ -318,10 +316,8 @@
                         return;
                     }
                     mAcquiredReceived = true;
-                    final UdfpsView view = mOverlay.getOverlayView();
-                    if (view != null && isOptical()) {
-                        unconfigureDisplay(view);
-                    }
+                    final View view = mOverlay.getTouchOverlay();
+                    unconfigureDisplay(view);
                     tryAodSendFingerUp();
                 });
             }
@@ -339,7 +335,9 @@
                 if (mOverlay == null || mOverlay.isHiding()) {
                     return;
                 }
-                mOverlay.getOverlayView().setDebugMessage(message);
+                if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+                    ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message);
+                }
             });
         }
 
@@ -506,6 +504,7 @@
         if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                 && !mAlternateBouncerInteractor.isVisibleState())
                 || mPrimaryBouncerInteractor.isInTransit()) {
+            Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor");
             return false;
         }
         if (event.getAction() == MotionEvent.ACTION_DOWN
@@ -563,7 +562,7 @@
                 }
                 mAttemptedToDismissKeyguard = false;
                 onFingerUp(requestId,
-                        mOverlay.getOverlayView(),
+                        mOverlay.getTouchOverlay(),
                         data.getPointerId(),
                         data.getX(),
                         data.getY(),
@@ -589,7 +588,8 @@
 
         // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
         if (mActivePointerId != MotionEvent.INVALID_POINTER_ID
-                || mAlternateBouncerInteractor.isVisibleState()) {
+                || (mAlternateBouncerInteractor.isVisibleState()
+                && !DeviceEntryUdfpsRefactor.isEnabled())) {
             shouldPilfer = true;
         }
 
@@ -597,7 +597,7 @@
         if (shouldPilfer && !mPointerPilfered
                 && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
             mInputManager.pilferPointers(
-                    mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+                    mOverlay.getTouchOverlay().getViewRootImpl().getInputToken());
             mPointerPilfered = true;
         }
 
@@ -605,9 +605,15 @@
     }
 
     private boolean shouldTryToDismissKeyguard() {
-        return mOverlay != null
-                && mOverlay.getAnimationViewController()
-                instanceof UdfpsKeyguardViewControllerAdapter
+        boolean onKeyguard = false;
+        if (DeviceEntryUdfpsRefactor.isEnabled()) {
+            onKeyguard = mKeyguardStateController.isShowing();
+        } else {
+            onKeyguard = mOverlay != null
+                    && mOverlay.getAnimationViewController()
+                        instanceof UdfpsKeyguardViewControllerAdapter;
+        }
+        return onKeyguard
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mAttemptedToDismissKeyguard;
     }
@@ -623,7 +629,6 @@
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull DumpManager dumpManager,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @NonNull FeatureFlags featureFlags,
             @NonNull FalsingManager falsingManager,
             @NonNull PowerManager powerManager,
             @NonNull AccessibilityManager accessibilityManager,
@@ -670,7 +675,6 @@
         mDumpManager = dumpManager;
         mDialogManager = dialogManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mFeatureFlags = featureFlags;
         mFalsingManager = falsingManager;
         mPowerManager = powerManager;
         mAccessibilityManager = accessibilityManager;
@@ -737,9 +741,9 @@
     @VisibleForTesting
     public void playStartHaptic() {
         if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            if (mOverlay != null && mOverlay.getOverlayView() != null) {
+            if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
                 mVibrator.performHapticFeedback(
-                        mOverlay.getOverlayView(),
+                        mOverlay.getTouchOverlay(),
                         HapticFeedbackConstants.CONTEXT_CLICK
                 );
             } else {
@@ -751,10 +755,11 @@
 
     @Override
     public void dozeTimeTick() {
-        if (mOverlay != null) {
-            final UdfpsView view = mOverlay.getOverlayView();
+        if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) {
+            DeviceEntryUdfpsRefactor.assertInLegacyMode();
+            final View view = mOverlay.getTouchOverlay();
             if (view != null) {
-                view.dozeTimeTick();
+                ((UdfpsView) view).dozeTimeTick();
             }
         }
     }
@@ -797,7 +802,7 @@
 
         if (mOverlay != null) {
             // Reset the controller back to its starting state.
-            final UdfpsView oldView = mOverlay.getOverlayView();
+            final View oldView = mOverlay.getTouchOverlay();
             if (oldView != null) {
                 onFingerUp(mOverlay.getRequestId(), oldView);
             }
@@ -813,9 +818,21 @@
 
     }
 
-    private void unconfigureDisplay(@NonNull UdfpsView view) {
-        if (view.isDisplayConfigured()) {
-            view.unconfigureDisplay();
+    private void unconfigureDisplay(View view) {
+        if (!isOptical()) {
+            return;
+        }
+        if (DeviceEntryUdfpsRefactor.isEnabled()) {
+            if (mUdfpsDisplayMode != null) {
+                mUdfpsDisplayMode.disable(null); // beverlt
+            }
+        } else {
+            if (view != null) {
+                UdfpsView udfpsView = (UdfpsView) view;
+                if (udfpsView.isDisplayConfigured()) {
+                    udfpsView.unconfigureDisplay();
+                }
+            }
         }
     }
 
@@ -837,10 +854,10 @@
             }
             mKeyguardViewManager.showPrimaryBouncer(true);
 
-            // play the same haptic as the LockIconViewController longpress
-            if (mOverlay != null && mOverlay.getOverlayView() != null) {
+            // play the same haptic as the DeviceEntryIcon longpress
+            if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
                 mVibrator.performHapticFeedback(
-                        mOverlay.getOverlayView(),
+                        mOverlay.getTouchOverlay(),
                         UdfpsController.LONG_PRESS
                 );
             } else {
@@ -909,8 +926,8 @@
             return;
         }
         cancelAodSendFingerUpAction();
-        if (mOverlay != null && mOverlay.getOverlayView() != null) {
-            onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
+        if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
+            onFingerUp(mOverlay.getRequestId(), mOverlay.getTouchOverlay());
         }
     }
 
@@ -996,20 +1013,22 @@
             playStartHaptic();
 
             mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
-            if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
-                mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-            }
         }
         mOnFingerDown = true;
         mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
                 minor, major, orientation, time, gestureStart, isAod);
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
-        final UdfpsView view = mOverlay.getOverlayView();
+
+        final View view = mOverlay.getTouchOverlay();
         if (view != null && isOptical()) {
             if (mIgnoreRefreshRate) {
                 dispatchOnUiReady(requestId);
             } else {
-                view.configureDisplay(() -> dispatchOnUiReady(requestId));
+                if (DeviceEntryUdfpsRefactor.isEnabled()) {
+                    mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId));
+                } else {
+                    ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId));
+                }
             }
         }
 
@@ -1018,7 +1037,7 @@
         }
     }
 
-    private void onFingerUp(long requestId, @NonNull UdfpsView view) {
+    private void onFingerUp(long requestId, @NonNull View view) {
         onFingerUp(
                 requestId,
                 view,
@@ -1035,7 +1054,7 @@
 
     private void onFingerUp(
             long requestId,
-            @NonNull UdfpsView view,
+            View view,
             int pointerId,
             float x,
             float y,
@@ -1056,9 +1075,7 @@
             }
         }
         mOnFingerDown = false;
-        if (isOptical()) {
-            unconfigureDisplay(view);
-        }
+        unconfigureDisplay(view);
         cancelAodSendFingerUpAction();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index a5bd89a..2d54f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,11 +46,11 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -96,7 +96,6 @@
     private val controllerCallback: IUdfpsOverlayControllerCallback,
     private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
-    private val featureFlags: FeatureFlags,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
@@ -104,9 +103,22 @@
     private val transitionInteractor: KeyguardTransitionInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
 ) {
-    /** The view, when [isShowing], or null. */
-    var overlayView: UdfpsView? = null
+    private var overlayViewLegacy: UdfpsView? = null
         private set
+    private var overlayTouchView: UdfpsTouchOverlay? = null
+
+    /**
+     * Get the current UDFPS overlay touch view which is a different View depending on whether
+     * the DeviceEntryUdfpsRefactor flag is enabled or not.
+     * @return The view, when [isShowing], else null
+     */
+    fun getTouchOverlay(): View? {
+        return if (DeviceEntryUdfpsRefactor.isEnabled) {
+            overlayTouchView
+        } else {
+            overlayViewLegacy
+        }
+    }
 
     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
     private var sensorBounds: Rect = Rect()
@@ -132,15 +144,15 @@
 
     /** If the overlay is currently showing. */
     val isShowing: Boolean
-        get() = overlayView != null
+        get() = getTouchOverlay() != null
 
     /** Opposite of [isShowing]. */
     val isHiding: Boolean
-        get() = overlayView == null
+        get() = getTouchOverlay() == null
 
     /** The animation controller if the overlay [isShowing]. */
     val animationViewController: UdfpsAnimationViewController<*>?
-        get() = overlayView?.animationViewController
+        get() = overlayViewLegacy?.animationViewController
 
     private var touchExplorationEnabled = false
 
@@ -158,28 +170,48 @@
     /** Show the overlay or return false and do nothing if it is already showing. */
     @SuppressLint("ClickableViewAccessibility")
     fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
-        if (overlayView == null) {
+        if (getTouchOverlay() == null) {
             overlayParams = params
             sensorBounds = Rect(params.sensorBounds)
             try {
-                overlayView = (inflater.inflate(
-                    R.layout.udfps_view, null, false
-                ) as UdfpsView).apply {
-                    overlayParams = params
-                    setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
-                    val animation = inflateUdfpsAnimation(this, controller)
-                    if (animation != null) {
-                        animation.init()
-                        animationViewController = animation
+                if (DeviceEntryUdfpsRefactor.isEnabled) {
+                    overlayTouchView = (inflater.inflate(
+                            R.layout.udfps_touch_overlay, null, false
+                    ) as UdfpsTouchOverlay).apply {
+                        // This view overlaps the sensor area
+                        // prevent it from being selectable during a11y
+                        if (requestReason.isImportantForAccessibility()) {
+                            importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+                        }
+                        windowManager.addView(this, coreLayoutParams.updateDimensions(null))
                     }
-                    // This view overlaps the sensor area
-                    // prevent it from being selectable during a11y
-                    if (requestReason.isImportantForAccessibility()) {
-                        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
-                    }
+                    // TODO (b/305234447): Bind view model to UdfpsTouchOverlay here to control
+                    // the visibility (sometimes, even if UDFPS is running, the UDFPS UI can be
+                    // obscured and we don't want to accept touches. ie: for enrollment don't accept
+                    // touches when the shade is expanded and for keyguard: don't accept touches
+                    // depending on the keyguard & shade state
+                } else {
+                    overlayViewLegacy = (inflater.inflate(
+                            R.layout.udfps_view, null, false
+                    ) as UdfpsView).apply {
+                        overlayParams = params
+                        setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
+                        val animation = inflateUdfpsAnimation(this, controller)
+                        if (animation != null) {
+                            animation.init()
+                            animationViewController = animation
+                        }
+                        // This view overlaps the sensor area
+                        // prevent it from being selectable during a11y
+                        if (requestReason.isImportantForAccessibility()) {
+                            importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+                        }
 
-                    windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
-                    sensorRect = sensorBounds
+                        windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+                        sensorRect = sensorBounds
+                    }
+                }
+                getTouchOverlay()?.apply {
                     touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
                     overlayTouchListener = TouchExplorationStateChangeListener {
                         if (accessibilityManager.isTouchExplorationEnabled) {
@@ -193,7 +225,7 @@
                         }
                     }
                     accessibilityManager.addTouchExplorationStateChangeListener(
-                        overlayTouchListener!!
+                            overlayTouchListener!!
                     )
                     overlayTouchListener?.onTouchExplorationStateChanged(true)
                 }
@@ -211,6 +243,8 @@
         view: UdfpsView,
         controller: UdfpsController
     ): UdfpsAnimationViewController<*>? {
+        DeviceEntryUdfpsRefactor.assertInLegacyMode()
+
         val isEnrollment = when (requestReason) {
             REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
             else -> false
@@ -237,39 +271,27 @@
                 )
             }
             REASON_AUTH_KEYGUARD -> {
-                if (DeviceEntryUdfpsRefactor.isEnabled) {
-                    // note: empty controller, currently shows no visual affordance
-                    // instead SysUI will show the fingerprint icon in its DeviceEntryIconView
-                    UdfpsBpViewController(
-                            view.addUdfpsView(R.layout.udfps_bp_view),
-                            statusBarStateController,
-                            primaryBouncerInteractor,
-                            dialogManager,
-                            dumpManager
-                    )
-                } else {
-                    UdfpsKeyguardViewControllerLegacy(
-                        view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
-                            updateSensorLocation(sensorBounds)
-                        },
-                        statusBarStateController,
-                        statusBarKeyguardViewManager,
-                        keyguardUpdateMonitor,
-                        dumpManager,
-                        transitionController,
-                        configurationController,
-                        keyguardStateController,
-                        unlockedScreenOffAnimationController,
-                        dialogManager,
-                        controller,
-                        activityLaunchAnimator,
-                        primaryBouncerInteractor,
-                        alternateBouncerInteractor,
-                        udfpsKeyguardAccessibilityDelegate,
-                        selectedUserInteractor,
-                        transitionInteractor,
-                    )
-                }
+                UdfpsKeyguardViewControllerLegacy(
+                    view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+                        updateSensorLocation(sensorBounds)
+                    },
+                    statusBarStateController,
+                    statusBarKeyguardViewManager,
+                    keyguardUpdateMonitor,
+                    dumpManager,
+                    transitionController,
+                    configurationController,
+                    keyguardStateController,
+                    unlockedScreenOffAnimationController,
+                    dialogManager,
+                    controller,
+                    activityLaunchAnimator,
+                    primaryBouncerInteractor,
+                    alternateBouncerInteractor,
+                    udfpsKeyguardAccessibilityDelegate,
+                    selectedUserInteractor,
+                    transitionInteractor,
+                )
             }
             REASON_AUTH_BP -> {
                 // note: empty controller, currently shows no visual affordance
@@ -302,19 +324,26 @@
     fun hide(): Boolean {
         val wasShowing = isShowing
 
-        overlayView?.apply {
+        overlayViewLegacy?.apply {
             if (isDisplayConfigured) {
                 unconfigureDisplay()
             }
+            animationViewController = null
+        }
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            udfpsDisplayModeProvider.disable(null)
+        }
+        getTouchOverlay()?.apply {
             windowManager.removeView(this)
             setOnTouchListener(null)
             setOnHoverListener(null)
-            animationViewController = null
             overlayTouchListener?.let {
                 accessibilityManager.removeTouchExplorationStateChangeListener(it)
             }
         }
-        overlayView = null
+
+        overlayViewLegacy = null
+        overlayTouchView = null
         overlayTouchListener = null
 
         return wasShowing
@@ -392,7 +421,14 @@
     }
 
     private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
-        if (animation !is UdfpsKeyguardViewControllerAdapter) {
+        val keyguardNotShowing =
+            if (DeviceEntryUdfpsRefactor.isEnabled) {
+                !keyguardStateController.isShowing
+            } else {
+                animation !is UdfpsKeyguardViewControllerAdapter
+            }
+
+        if (keyguardNotShowing) {
             // always rotate view if we're not on the keyguard
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 35c3ded..6954eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -50,7 +50,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 
 /** Class that coordinates non-HBM animations during keyguard authentication. */
@@ -215,12 +214,12 @@
     suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.dozeAmountTransition.collect { transitionStep ->
-                  view.onDozeAmountChanged(
-                      transitionStep.value,
-                      transitionStep.value,
-                      UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
-                  )
-              }
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                )
+            }
         }
     }
 
@@ -286,7 +285,6 @@
         keyguardStateController.removeCallback(keyguardStateControllerCallback)
         statusBarStateController.removeCallback(stateListener)
         keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
-        keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         configurationController.removeCallback(configurationListener)
         if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
             lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
@@ -334,14 +332,9 @@
             if (udfpsAffordanceWasNotShowing) {
                 view.animateInUdfpsBouncer(null)
             }
-            if (keyguardStateController.isOccluded) {
-                keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
-            }
             view.announceForAccessibility(
                 view.context.getString(R.string.accessibility_fingerprint_bouncer)
             )
-        } else {
-            keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         }
         updateAlpha()
         updatePauseAuth()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 050b399..ff23837 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -30,13 +30,16 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 
 /** Repository for the current state of the display */
@@ -66,7 +69,8 @@
     deviceStateManager: DeviceStateManager,
     displayManager: DisplayManager,
     @Main handler: Handler,
-    @Main mainExecutor: Executor
+    @Background backgroundExecutor: Executor,
+    @Background backgroundDispatcher: CoroutineDispatcher,
 ) : DisplayStateRepository {
     override val isReverseDefaultRotation =
         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
@@ -94,9 +98,10 @@
                     }
 
                 sendRearDisplayStateUpdate(false)
-                deviceStateManager.registerCallback(mainExecutor, callback)
+                deviceStateManager.registerCallback(backgroundExecutor, callback)
                 awaitClose { deviceStateManager.unregisterCallback(callback) }
             }
+            .flowOn(backgroundDispatcher)
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
@@ -137,6 +142,7 @@
                 )
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
+            .flowOn(backgroundDispatcher)
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
index 039078e..2484c33 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt
@@ -12,20 +12,15 @@
  * 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.view
+package com.android.systemui.biometrics.ui.view
 
 import android.content.Context
 import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.android.systemui.res.R
+import android.widget.FrameLayout
 
-/** Wraps around a widget rendered in communal mode. */
-class CommunalWidgetWrapper(context: Context, attrs: AttributeSet? = null) :
-    LinearLayout(context, attrs) {
-    init {
-        id = R.id.communal_widget_wrapper
-    }
-}
+/**
+ * A translucent (not visible to the user) view that receives touches to send to FingerprintManager
+ * for fingerprint authentication.
+ */
+class UdfpsTouchOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 11a5d8b..3defec5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -485,8 +485,7 @@
     ): Int =
         if (isPendingConfirmation) {
             when (sensorType) {
-                FingerprintSensorType.POWER_BUTTON ->
-                    R.string.security_settings_sfps_enroll_find_sensor_message
+                FingerprintSensorType.POWER_BUTTON -> -1
                 else -> R.string.fingerprint_dialog_authenticated_confirmation
             }
         } else if (isAuthenticating || isAuthenticated) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index c0b2153..1e0e16c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.log.dagger.BouncerTableLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
@@ -208,6 +209,7 @@
     }
 
     override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        DeviceEntryUdfpsRefactor.assertInLegacyMode()
         _alternateBouncerUIAvailable.value = isAvailable
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 9a7fec1..a721100 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -17,14 +17,23 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 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.BiometricSettingsRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
 @SysUISingleton
@@ -34,13 +43,29 @@
     private val statusBarStateController: StatusBarStateController,
     private val keyguardStateController: KeyguardStateController,
     private val bouncerRepository: KeyguardBouncerRepository,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val systemClock: SystemClock,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    @Application scope: CoroutineScope,
 ) {
     var receivedDownTouch = false
     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
     private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
+    private val alternateBouncerSupported: StateFlow<Boolean> =
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            fingerprintPropertyRepository.sensorType
+                .map { sensorType ->
+                    sensorType.isUdfps() || sensorType == FingerprintSensorType.POWER_BUTTON
+                }
+                .stateIn(
+                    scope = scope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = false,
+                )
+        } else {
+            bouncerRepository.alternateBouncerUIAvailable
+        }
 
     /**
      * Sets the correct bouncer states to show the alternate bouncer if it can show.
@@ -71,6 +96,7 @@
     }
 
     fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) {
+        DeviceEntryUdfpsRefactor.assertInLegacyMode()
         if (isAvailable) {
             alternateBouncerUiAvailableFromSource.add(token)
         } else {
@@ -82,7 +108,7 @@
     }
 
     fun canShowAlternateBouncerForFingerprint(): Boolean {
-        return bouncerRepository.alternateBouncerUIAvailable.value &&
+        return alternateBouncerSupported.value &&
             biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
             !keyguardUpdateMonitor.isFingerprintLockedOut &&
             !keyguardStateController.isUnlocked &&
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index d5ac483..b598631 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -51,6 +52,7 @@
     @Application private val applicationContext: Context,
     private val repository: BouncerRepository,
     private val authenticationInteractor: AuthenticationInteractor,
+    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
     flags: SceneContainerFlags,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
@@ -131,6 +133,7 @@
      * user's pocket or by the user's face while holding their device up to their ear.
      */
     fun onIntentionalUserInput() {
+        keyguardFaceAuthInteractor.onPrimaryBouncerUserInput()
         powerInteractor.onUserTouch()
         falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 56dfa5ed..aa7758f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.shared.system.SysUiStatsLog
@@ -75,6 +76,7 @@
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
 ) {
     private val passiveAuthBouncerDelay =
         context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -414,15 +416,12 @@
 
     /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
     private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
-        val canRunFaceAuth =
-            keyguardStateController.isFaceEnrolled &&
-                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
-                keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()
         val canRunActiveUnlock =
             currentUserActiveUnlockRunning &&
                 keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
 
-        return !needsFullscreenBouncer() && (canRunFaceAuth || canRunActiveUnlock)
+        return !needsFullscreenBouncer() &&
+            (keyguardFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
new file mode 100644
index 0000000..5385442
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.helper
+
+import androidx.annotation.VisibleForTesting
+
+/** Enumerates all known adaptive layout configurations. */
+enum class BouncerSceneLayout {
+    /** The default UI with the bouncer laid out normally. */
+    STANDARD,
+    /** The bouncer is displayed vertically stacked with the user switcher. */
+    STACKED,
+    /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
+    SIDE_BY_SIDE,
+    /** The bouncer is split in two with both sides shown side-by-side. */
+    SPLIT,
+}
+
+/** Enumerates the supported window size classes. */
+enum class SizeClass {
+    COMPACT,
+    MEDIUM,
+    EXPANDED,
+}
+
+/**
+ * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow
+ * for testing that's not dependent on Compose.
+ */
+@VisibleForTesting
+fun calculateLayoutInternal(
+    width: SizeClass,
+    height: SizeClass,
+    isSideBySideSupported: Boolean,
+): BouncerSceneLayout {
+    return when (height) {
+        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+        SizeClass.MEDIUM ->
+            when (width) {
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
+                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
+                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+            }
+        SizeClass.EXPANDED ->
+            when (width) {
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
+                SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
+                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+            }
+    }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
+        ?: BouncerSceneLayout.STANDARD
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index ed6a48f..b1c5ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -94,24 +94,23 @@
      * @param yPx The vertical coordinate of the position of the user's pointer, in pixels.
      * @param containerSizePx The size of the container of the dot grid, in pixels. It's assumed
      *   that the dot grid is perfectly square such that width and height are equal.
-     * @param verticalOffsetPx How far down from `0` does the dot grid start on the display.
      */
-    fun onDrag(xPx: Float, yPx: Float, containerSizePx: Int, verticalOffsetPx: Float) {
+    fun onDrag(xPx: Float, yPx: Float, containerSizePx: Int) {
         val cellWidthPx = containerSizePx / columnCount
         val cellHeightPx = containerSizePx / rowCount
 
-        if (xPx < 0 || yPx < verticalOffsetPx) {
+        if (xPx < 0 || yPx < 0) {
             return
         }
 
         val dotColumn = (xPx / cellWidthPx).toInt()
-        val dotRow = ((yPx - verticalOffsetPx) / cellHeightPx).toInt()
+        val dotRow = (yPx / cellHeightPx).toInt()
         if (dotColumn > columnCount - 1 || dotRow > rowCount - 1) {
             return
         }
 
         val dotPixelX = dotColumn * cellWidthPx + cellWidthPx / 2
-        val dotPixelY = dotRow * cellHeightPx + cellHeightPx / 2 + verticalOffsetPx
+        val dotPixelY = dotRow * cellHeightPx + cellHeightPx / 2
 
         val distance = sqrt((xPx - dotPixelX).pow(2) + (yPx - dotPixelY).pow(2))
         val hitRadius = hitFactor * min(cellWidthPx, cellHeightPx) / 2
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index 334cf93..740e8bb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -45,6 +45,7 @@
     public static final int BACK_GESTURE = 16;
     public static final int QS_SWIPE_NESTED = 17;
     public static final int MEDIA_SEEKBAR = 18;
+    public static final int ALTERNATE_BOUNCER_SWIPE = 19;
 
     @IntDef({
             QUICK_SETTINGS,
@@ -65,6 +66,7 @@
             QS_SWIPE_NESTED,
             BACK_GESTURE,
             MEDIA_SEEKBAR,
+            ALTERNATE_BOUNCER_SWIPE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InteractionType {}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 15e2e9a..b13bf4e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -22,6 +22,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN;
+import static com.android.systemui.classifier.Classifier.ALTERNATE_BOUNCER_SWIPE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
@@ -159,7 +160,8 @@
                 || interactionType == QS_COLLAPSE
                 || interactionType == Classifier.UDFPS_AUTHENTICATION
                 || interactionType == Classifier.QS_SWIPE_SIDE
-                || interactionType == QS_SWIPE_NESTED) {
+                || interactionType == QS_SWIPE_NESTED
+                || interactionType == ALTERNATE_BOUNCER_SWIPE) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index 2fb6aaf..93aa279 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -17,6 +17,7 @@
 package com.android.systemui.classifier;
 
 
+import static com.android.systemui.classifier.Classifier.ALTERNATE_BOUNCER_SWIPE;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
@@ -73,6 +74,7 @@
             case NOTIFICATION_DISMISS:
                 wrongDirection = vertical;
                 break;
+            case ALTERNATE_BOUNCER_SWIPE:
             case UNLOCK:
             case BOUNCER_UNLOCK:
                 wrongDirection = !vertical || !up;
diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
rename to packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 5e6caf0..27c9b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -11,20 +11,17 @@
  * 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
+ * limitations under the License.
  */
-package com.android.systemui.common
 
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
+package com.android.systemui.common.data
+
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
-abstract class CommonModule {
+abstract class CommonDataLayerModule {
     @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-
-    @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
copy to packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
index 5e6caf0..7be2eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
@@ -11,20 +11,17 @@
  * 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
+ * limitations under the License.
  */
-package com.android.systemui.common
+
+package com.android.systemui.common.domain
 
 import com.android.systemui.common.domain.interactor.ConfigurationInteractor
 import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
-abstract class CommonModule {
-    @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-
+abstract class CommonDomainLayerModule {
     @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index 6c45af2..3cdb573 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -36,3 +36,7 @@
         override val contentDescription: ContentDescription?,
     ) : Icon()
 }
+
+/** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */
+fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon =
+    Icon.Loaded(this, contentDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
new file mode 100644
index 0000000..fdd98bec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.shared.model
+
+/** Models the bounds of the notification container. */
+data class NotificationContainerBounds(
+    /** The position of the top of the container in its window coordinate system, in pixels. */
+    val top: Float = 0f,
+    /** The position of the bottom of the container in its window coordinate system, in pixels. */
+    val bottom: Float = 0f,
+    /** Whether any modifications to top/bottom should be smoothly animated. */
+    val isAnimated: Boolean = false,
+) {
+    /** The current height of the notification container. */
+    val height: Float = bottom - top
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 7bca86e..12be32c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -31,8 +31,10 @@
 import com.android.systemui.util.kotlin.emitOnStart
 import com.android.systemui.util.view.bindLatest
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
@@ -95,7 +97,8 @@
  * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
  * [onInflate] when done.
  *
- * This never completes unless cancelled, it just suspends and waits for updates.
+ * This never completes unless cancelled, it just suspends and waits for updates. It runs on a
+ * background thread using [backgroundDispatcher].
  *
  * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
  *
@@ -105,7 +108,7 @@
  * ```
  * parentView.repeatWhenAttached {
  *     configurationState
- *         .reinflateOnChange(
+ *         .reinflateAndBindLatest(
  *             R.layout.my_layout,
  *             parentView,
  *             attachToRoot = false,
@@ -124,7 +127,10 @@
     @LayoutRes resource: Int,
     root: ViewGroup?,
     attachToRoot: Boolean,
+    backgroundDispatcher: CoroutineDispatcher,
     onInflate: (T) -> DisposableHandle?,
 ) {
-    inflateLayout<T>(resource, root, attachToRoot).bindLatest(onInflate)
+    inflateLayout<T>(resource, root, attachToRoot)
+        .flowOn(backgroundDispatcher)
+        .bindLatest(onInflate)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
new file mode 100644
index 0000000..8d04e3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.view
+
+import android.view.View
+
+/**
+ * Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or
+ * [View#IMPORTANT_FOR_ACCESSIBILITY_NO] based on [value].
+ */
+fun View.setImportantForAccessibilityYesNo(value: Boolean) {
+    importantForAccessibility =
+        if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO
+}
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 e50850d..a12db6f 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
@@ -91,7 +91,8 @@
 interface CommunalWidgetDao {
     @Query(
         "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
-            "ON communal_item_rank_table.uid = communal_widget_table.item_id"
+            "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
+            "ORDER BY communal_item_rank_table.rank DESC"
     )
     fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
 
@@ -112,6 +113,17 @@
     @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
     fun insertItemRank(rank: Int): Long
 
+    @Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid")
+    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)
+        }
+    }
+
     @Transaction
     fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long {
         return insertWidget(
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 f7fee96..ded5581 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
@@ -61,6 +61,9 @@
 
     /** 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>) {}
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -165,6 +168,15 @@
         }
     }
 
+    override fun updateWidgetOrder(ids: List<Int>) {
+        applicationScope.launch(bgDispatcher) {
+            communalWidgetDao.updateWidgetOrder(ids)
+            logger.i({ "Updated the order of widget list with ids: $str1." }) {
+                str1 = ids.toString()
+            }
+        }
+    }
+
     private fun mapToContentModel(
         entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
     ): CommunalWidgetContentModel {
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 7391a5e..e630fd4 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
@@ -27,12 +27,12 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -47,7 +47,7 @@
     private val widgetRepository: CommunalWidgetRepository,
     mediaRepository: CommunalMediaRepository,
     smartspaceRepository: SmartspaceRepository,
-    tutorialInteractor: CommunalTutorialInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val appWidgetHost: AppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter
 ) {
@@ -69,6 +69,8 @@
     val isCommunalShowing: Flow<Boolean> =
         communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
 
+    val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
+
     /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
     fun onSceneChanged(newScene: CommunalSceneKey) {
         communalRepository.setDesiredScene(newScene)
@@ -86,20 +88,11 @@
     /** Delete a widget by id. */
     fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
 
-    /** A list of all the communal content to be displayed in the communal hub. */
-    @OptIn(ExperimentalCoroutinesApi::class)
-    val communalContent: Flow<List<CommunalContentModel>> =
-        tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
-            if (isTutorialMode) {
-                return@flatMapLatest flowOf(tutorialContent)
-            }
-            combine(smartspaceContent, umoContent, widgetContent) { smartspace, umo, widgets ->
-                smartspace + umo + widgets
-            }
-        }
+    /** Reorder widgets. The order in the list will be their display order in the hub. */
+    fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
 
     /** A list of widget content to be displayed in the communal hub. */
-    private val widgetContent: Flow<List<CommunalContentModel.Widget>> =
+    val widgetContent: Flow<List<CommunalContentModel.Widget>> =
         widgetRepository.communalWidgets.map { widgets ->
             widgets.map Widget@{ widget ->
                 return@Widget CommunalContentModel.Widget(
@@ -111,7 +104,7 @@
         }
 
     /** A flow of available smartspace content. Currently only showing timer targets. */
-    private val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+    val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
         if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
             flowOf(emptyList())
         } else {
@@ -133,7 +126,7 @@
         }
 
     /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
-    private val tutorialContent: List<CommunalContentModel.Tutorial> =
+    val tutorialContent: List<CommunalContentModel.Tutorial> =
         listOf(
             CommunalContentModel.Tutorial(id = 0, CommunalContentSize.FULL),
             CommunalContentModel.Tutorial(id = 1, CommunalContentSize.THIRD),
@@ -145,7 +138,7 @@
             CommunalContentModel.Tutorial(id = 7, CommunalContentSize.HALF),
         )
 
-    private val umoContent: Flow<List<CommunalContentModel.Umo>> =
+    val umoContent: Flow<List<CommunalContentModel.Umo>> =
         mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
             if (mediaPlaying) {
                 // TODO(b/310254801): support HALF and FULL layouts
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
deleted file mode 100644
index 0daf7b5..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ /dev/null
@@ -1,80 +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.systemui.communal.ui.adapter
-
-import android.appwidget.AppWidgetHost
-import android.appwidget.AppWidgetManager
-import android.content.Context
-import android.util.SizeF
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
-import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.Logger
-import com.android.systemui.log.dagger.CommunalLog
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Transforms a [CommunalAppWidgetInfo] to a view that renders the widget. */
-class CommunalWidgetViewAdapter
-@Inject
-constructor(
-    @Application private val context: Context,
-    private val appWidgetManager: AppWidgetManager,
-    private val appWidgetHost: AppWidgetHost,
-    @CommunalLog logBuffer: LogBuffer,
-) {
-    companion object {
-        private const val TAG = "CommunalWidgetViewAdapter"
-    }
-
-    private val logger = Logger(logBuffer, TAG)
-
-    fun adapt(providerInfoFlow: Flow<CommunalAppWidgetInfo?>): Flow<CommunalWidgetWrapper?> =
-        providerInfoFlow.map {
-            if (it == null) {
-                return@map null
-            }
-
-            val appWidgetId = it.appWidgetId
-            val providerInfo = it.providerInfo
-
-            if (appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, providerInfo.provider)) {
-                logger.d("Success binding app widget id: $appWidgetId")
-                return@map CommunalWidgetWrapper(context).apply {
-                    addView(
-                        appWidgetHost.createView(context, appWidgetId, providerInfo).apply {
-                            // Set the widget to minimum width and height
-                            updateAppWidgetSize(
-                                appWidgetManager.getAppWidgetOptions(appWidgetId),
-                                listOf(
-                                    SizeF(
-                                        providerInfo.minResizeWidth.toFloat(),
-                                        providerInfo.minResizeHeight.toFloat()
-                                    )
-                                )
-                            )
-                        }
-                    )
-                }
-            } else {
-                logger.w("Failed binding app widget id")
-                return@map null
-            }
-        }
-}
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
new file mode 100644
index 0000000..4d8e893
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.media.controls.ui.MediaHost
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** The base view model for the communal hub. */
+abstract class BaseCommunalViewModel(
+    private val communalInteractor: CommunalInteractor,
+    val mediaHost: MediaHost,
+) {
+    val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
+
+    val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+
+    fun onSceneChanged(scene: CommunalSceneKey) {
+        communalInteractor.onSceneChanged(scene)
+    }
+
+    /** A list of all the communal content to be displayed in the communal hub. */
+    abstract val communalContent: Flow<List<CommunalContentModel>>
+
+    /** Whether in edit mode for the communal hub. */
+    open val isEditMode = false
+
+    /** 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 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
new file mode 100644
index 0000000..111f8b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.dagger.MediaModule
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
+
+/** The view model for communal hub in edit mode. */
+@SysUISingleton
+class CommunalEditModeViewModel
+@Inject
+constructor(
+    private val communalInteractor: CommunalInteractor,
+    @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+    override val isEditMode = true
+
+    // Only widgets are editable.
+    override val communalContent: Flow<List<CommunalContentModel>> =
+        communalInteractor.widgetContent
+
+    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+
+    override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 14edc8e..11bde6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -17,34 +17,42 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 
+/** The default view model used for showing the communal hub. */
 @SysUISingleton
 class CommunalViewModel
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
-    @Named(MediaModule.COMMUNAL_HUB) val mediaHost: MediaHost,
-) {
-    val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
-    fun onSceneChanged(scene: CommunalSceneKey) {
-        communalInteractor.onSceneChanged(scene)
-    }
+    tutorialInteractor: CommunalTutorialInteractor,
+    @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override val communalContent: Flow<List<CommunalContentModel>> =
+        tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
+            if (isTutorialMode) {
+                return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+            }
+            combine(
+                communalInteractor.smartspaceContent,
+                communalInteractor.umoContent,
+                communalInteractor.widgetContent,
+            ) { smartspace, umo, widgets ->
+                smartspace + umo + widgets
+            }
+        }
 
-    /** A list of all the communal content to be displayed in the communal hub. */
-    val communalContent: Flow<List<CommunalContentModel>> = communalInteractor.communalContent
-
-    /** Delete a widget by id. */
-    fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
-
-    /** Open the widget editor */
-    fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+    override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
 }
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 78e85db..7b94fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -20,17 +20,21 @@
 import android.content.Intent
 import android.os.Bundle
 import android.util.Log
-import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.res.R
+import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
 import javax.inject.Inject
 
 /** An Activity for editing the widgets that appear in hub mode. */
-class EditWidgetsActivity @Inject constructor(private val communalInteractor: CommunalInteractor) :
-    ComponentActivity() {
+class EditWidgetsActivity
+@Inject
+constructor(
+    private val communalViewModel: CommunalEditModeViewModel,
+    private val communalInteractor: CommunalInteractor,
+) : ComponentActivity() {
     companion object {
         /**
          * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode.
@@ -59,20 +63,19 @@
                         "Failed to receive result from widget picker, code=${result.resultCode}"
                     )
             }
-            this@EditWidgetsActivity.finish()
         }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        setShowWhenLocked(true)
-        setContentView(R.layout.edit_widgets)
-
-        val addWidgetsButton = findViewById<View>(R.id.add_widget)
-        addWidgetsButton?.setOnClickListener({
-            addWidgetActivityLauncher.launch(
-                Intent(applicationContext, WidgetPickerActivity::class.java)
-            )
-        })
+        setCommunalEditWidgetActivityContent(
+            activity = this,
+            viewModel = communalViewModel,
+            onOpenWidgetPicker = {
+                addWidgetActivityLauncher.launch(
+                    Intent(applicationContext, WidgetPickerActivity::class.java)
+                )
+            },
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
index 3e6dbd5..a276548 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
@@ -43,7 +43,6 @@
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.widget_picker)
-        setShowWhenLocked(true)
 
         loadWidgets()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 4bdea75..65d4495 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,7 +22,7 @@
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
@@ -58,6 +58,13 @@
         onResult: (PeopleViewModel.Result) -> Unit,
     )
 
+    /** Bind the content of [activity] to [viewModel]. */
+    fun setCommunalEditWidgetActivityContent(
+        activity: ComponentActivity,
+        viewModel: BaseCommunalViewModel,
+        onOpenWidgetPicker: () -> Unit,
+    )
+
     /** Create a [View] to represent [viewModel] on screen. */
     fun createFooterActionsView(
         context: Context,
@@ -77,9 +84,9 @@
     /** Create a [View] to represent [viewModel] on screen. */
     fun createCommunalView(
         context: Context,
-        viewModel: CommunalViewModel,
+        viewModel: BaseCommunalViewModel,
     ): View
 
     /** Creates a container that hosts the communal UI and handles gesture transitions. */
-    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
+    fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index dcacd09..e7b8773 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -19,6 +19,7 @@
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
 import com.android.systemui.dagger.qualifiers.PerUser;
@@ -34,10 +35,9 @@
 import com.android.systemui.unfold.FoldStateLogger;
 import com.android.systemui.unfold.FoldStateLoggingProvider;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.unfold.UnfoldLatencyTracker;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.dagger.UnfoldBg;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
-import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -62,7 +62,7 @@
 
 /**
  * An example Dagger Subcomponent for Core SysUI.
- *
+ * <p>
  * See {@link ReferenceSysUIComponent} for the one actually used by AOSP.
  */
 @SysUISingleton
@@ -131,23 +131,34 @@
     default void init() {
         // Initialize components that have no direct tie to the dagger dependency graph,
         // but are critical to this component's operation
-        getSysUIUnfoldComponent().ifPresent(c -> {
-            c.getUnfoldLightRevealOverlayAnimation().init();
-            c.getUnfoldTransitionWallpaperController().init();
-            c.getUnfoldHapticsPlayer();
-        });
-        getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
+        getSysUIUnfoldComponent()
+                .ifPresent(
+                        c -> {
+                            c.getUnfoldLightRevealOverlayAnimation().init();
+                            c.getUnfoldTransitionWallpaperController().init();
+                            c.getUnfoldHapticsPlayer();
+                            c.getNaturalRotationUnfoldProgressProvider().init();
+                            c.getUnfoldLatencyTracker().init();
+                        });
         // No init method needed, just needs to be gotten so that it's created.
         getMediaMuteAwaitConnectionCli();
         getNearbyMediaDevicesManager();
-        getUnfoldLatencyTracker().init();
         getConnectingDisplayViewModel().init();
         getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
         getFoldStateLogger().ifPresent(FoldStateLogger::init);
-        getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
-                getUnfoldTransitionProgressForwarder().ifPresent((forwarder) ->
-                        progressProvider.addCallback(forwarder)
-                ));
+
+        Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider;
+
+        if (Flags.unfoldAnimationBackgroundProgress()) {
+            unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider();
+        } else {
+            unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider();
+        }
+        unfoldTransitionProgressProvider
+                .ifPresent(
+                        (progressProvider) ->
+                                getUnfoldTransitionProgressForwarder()
+                                        .ifPresent(progressProvider::addCallback));
     }
 
     /**
@@ -169,13 +180,14 @@
     ContextComponentHelper getContextComponentHelper();
 
     /**
-     * Creates a UnfoldLatencyTracker.
+     * Creates a UnfoldTransitionProgressProvider that calculates progress in the background.
      */
     @SysUISingleton
-    UnfoldLatencyTracker getUnfoldLatencyTracker();
+    @UnfoldBg
+    Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider();
 
     /**
-     * Creates a UnfoldTransitionProgressProvider.
+     * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread.
      */
     @SysUISingleton
     Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
@@ -219,11 +231,6 @@
      */
     Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent();
 
-    /**
-     * For devices with a hinge: the rotation animation
-     */
-    Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
-
     /** */
     MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a0e944b..d041acb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.keyguard.KeyguardViewConfigurator
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
-import com.android.systemui.keyguard.ui.binder.AlternateBouncerBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardDismissActionBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardDismissBinder
 import com.android.systemui.log.SessionTracker
@@ -92,11 +91,6 @@
     @ClassKey(AuthController::class)
     abstract fun bindAuthController(service: AuthController): CoreStartable
 
-    @Binds
-    @IntoMap
-    @ClassKey(AlternateBouncerBinder::class)
-    abstract fun bindAlternateBouncerBinder(impl: AlternateBouncerBinder): CoreStartable
-
     /** Inject into BiometricNotificationService */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 5f54a98..0405ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,7 +42,8 @@
 import com.android.systemui.bouncer.ui.BouncerViewModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
-import com.android.systemui.common.CommonModule;
+import com.android.systemui.common.data.CommonDataLayerModule;
+import com.android.systemui.common.domain.CommonDomainLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.controls.dagger.ControlsModule;
@@ -106,8 +107,6 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
@@ -178,7 +177,8 @@
         ClipboardOverlayModule.class,
         ClockRegistryModule.class,
         CommunalModule.class,
-        CommonModule.class,
+        CommonDataLayerModule.class,
+        CommonDomainLayerModule.class,
         ConnectivityModule.class,
         ControlsModule.class,
         CoroutinesModule.class,
@@ -374,11 +374,4 @@
     @Binds
     abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
             LargeScreenShadeInterpolatorImpl impl);
-
-    @SysUISingleton
-    @Provides
-    static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
-            NotificationInterruptStateProvider innerProvider) {
-        return new NotificationInterruptStateProviderWrapper(innerProvider);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 31a5d37..4bfc9484 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -97,7 +97,7 @@
         }
 
     fun canShowFaceScanningAnim(): Boolean {
-        return hasProviders && keyguardUpdateMonitor.isFaceEnrolled
+        return hasProviders && keyguardUpdateMonitor.isFaceEnabledAndEnrolled
     }
 
     fun shouldShowFaceScanningAnim(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 715fb17..4cddb9c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -103,8 +103,11 @@
                 initialValue = false,
             )
 
-    // Authenticated by a TrustAgent like trusted device, location, etc or by face auth.
-    private val passivelyAuthenticated =
+    /**
+     * Whether the user is currently authenticated by a TrustAgent like trusted device, location,
+     * etc., or by face auth.
+     */
+    private val isPassivelyAuthenticated =
         merge(
                 trustRepository.isCurrentUserTrusted,
                 deviceEntryFaceAuthRepository.isAuthenticated,
@@ -117,25 +120,31 @@
      * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been
      * dismissed.
      *
+     * A value of `null` is meaningless and is used as placeholder while the actual value is still
+     * being loaded in the background.
+     *
      * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
      * UI.
      */
-    val canSwipeToEnter =
+    val canSwipeToEnter: StateFlow<Boolean?> =
         combine(
                 // This is true when the user has chosen to show the lockscreen but has not made it
                 // secure.
                 authenticationInteractor.authenticationMethod.map {
                     it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
                 },
-                passivelyAuthenticated,
+                isPassivelyAuthenticated,
                 isDeviceEntered
-            ) { isSwipeAuthMethod, passivelyAuthenticated, isDeviceEntered ->
-                (isSwipeAuthMethod || passivelyAuthenticated) && !isDeviceEntered
+            ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered ->
+                (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = false,
+                // Starts as null to prevent downstream collectors from falsely assuming that the
+                // user can or cannot swipe to enter the device while the real value is being loaded
+                // from upstream data sources.
+                initialValue = null,
             )
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 26c5ea6..c93b8e1 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -29,7 +29,6 @@
 import com.android.app.tracing.traceSection
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.DisplayEvent
 import com.android.systemui.util.Compile
@@ -93,7 +92,7 @@
 constructor(
     private val displayManager: DisplayManager,
     @Background backgroundHandler: Handler,
-    @Application applicationScope: CoroutineScope,
+    @Background bgApplicationScope: CoroutineScope,
     @Background backgroundCoroutineDispatcher: CoroutineDispatcher
 ) : DisplayRepository {
     private val allDisplayEvents: Flow<DisplayEvent> =
@@ -141,8 +140,7 @@
     private val enabledDisplays =
         allDisplayEvents
             .map { getDisplays() }
-            .flowOn(backgroundCoroutineDispatcher)
-            .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+            .shareIn(bgApplicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
 
     override val displays: Flow<Set<Display>> = enabledDisplays
 
@@ -203,9 +201,8 @@
             }
             .distinctUntilChanged()
             .debugLog("connectedDisplayIds")
-            .flowOn(backgroundCoroutineDispatcher)
             .stateIn(
-                applicationScope,
+                bgApplicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 // The initial value is set to empty, but connected displays are gathered as soon as
                 // the flow starts being collected. This is to ensure the call to get displays (an
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 83c16ae..6a0e882 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import javax.inject.Inject
 
 /** A class in which engineers can define flag dependencies */
@@ -26,6 +27,7 @@
 class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
     FlagDependenciesBase(featureFlags, handler) {
     override fun defineDependencies() {
+        NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 093319f..0c24752 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@
     // 100 - notification
     // TODO(b/297792660): Tracking Bug
     @JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
-        unreleasedFlag("uncleared_transient_hun_fix", teamfood = false)
+        unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
 
     // TODO(b/298308067): Tracking Bug
     @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
-        unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = false)
+        unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
 
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -148,26 +148,10 @@
     // TODO(b/255607168): Tracking Bug
     @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1")
 
-    /**
-     * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
-     * new KeyguardTransitionRepository.
-     */
-    // TODO(b/281655028): Tracking bug
-    @JvmField
-    val LIGHT_REVEAL_MIGRATION = unreleasedFlag("light_reveal_migration", teamfood = true)
-
-    // TODO(b/301915812): Tracking Bug
-    @JvmField
-    val NEW_AOD_TRANSITION = unreleasedFlag("new_aod_transition", teamfood = true)
-
     // TODO(b/305984787):
     @JvmField
     val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true)
 
-    /** Flag to control the migration of face auth to modern architecture. */
-    // TODO(b/262838215): Tracking bug
-    @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
-
     /** Flag to control the revamp of keyguard biometrics progress animation */
     // TODO(b/244313043): Tracking bug
     @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
@@ -338,6 +322,10 @@
     // TODO(b/301610137): Tracking bug
     @JvmField val NEW_NETWORK_SLICE_UI = releasedFlag("new_network_slice_ui")
 
+    // TODO(b/311222557): Tracking bug
+    val ROAMING_INDICATOR_VIA_DISPLAY_INFO =
+        releasedFlag("roaming_indicator_via_display_info")
+
     // TODO(b/308138154): Tracking bug
     val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
         releasedFlag("filter_provisioning_network_subscriptions")
@@ -472,12 +460,6 @@
     val WALLPAPER_MULTI_CROP =
         sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
 
-    // TODO(b/290220798): Tracking Bug
-    @Keep
-    @JvmField
-    val ENABLE_PIP2_IMPLEMENTATION =
-        sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false)
-
     // 1200 - predictive back
     @Keep
     @JvmField
@@ -563,28 +545,12 @@
             unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     /**
-     * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be
-     * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove
-     * unused code from our APK at compile time.
+     * Whether the scene container (Flexiglass) is enabled. Note that SceneContainerFlags#isEnabled
+     * should be checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can
+     * remove unused code from our APK at compile time.
      */
     // TODO(b/283300105): Tracking Bug
     @JvmField val SCENE_CONTAINER_ENABLED = false
-    @Deprecated(
-        message = """
-            Do not use this flag directly. Please use
-            [com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled].
-
-            (Not really deprecated but using this as a simple way to bring attention to the above).
-        """,
-        replaceWith = ReplaceWith(
-            "com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled",
-        ),
-        level = DeprecationLevel.WARNING,
-    )
-    @JvmField val SCENE_CONTAINER = resourceBooleanFlag(
-        R.bool.config_sceneContainerFrameworkEnabled,
-        "scene_container",
-    )
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
@@ -644,7 +610,7 @@
 
     // TODO(b/277201412): Tracking Bug
     @JvmField
-    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag("split_shade_subpixel_optimization")
+    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag("split_shade_subpixel_optimization")
 
     // TODO(b/288868056): Tracking Bug
     @JvmField
@@ -721,7 +687,7 @@
 
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
     @JvmField
-    val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
+    val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
 
     // TODO(b/300995746): Tracking Bug
     /** A resource flag for whether the communal service is enabled. */
diff --git a/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt
new file mode 100644
index 0000000..bc1cc4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.fold.ui.helper
+
+import androidx.annotation.VisibleForTesting
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowLayoutInfo
+
+sealed interface FoldPosture {
+    /** A foldable device that's fully closed/folded or a device that doesn't support folding. */
+    data object Folded : FoldPosture
+    /** A foldable that's halfway open with the hinge held vertically. */
+    data object Book : FoldPosture
+    /** A foldable that's halfway open with the hinge held horizontally. */
+    data object Tabletop : FoldPosture
+    /** A foldable that's fully unfolded / flat. */
+    data object FullyUnfolded : FoldPosture
+}
+
+/**
+ * Internal version of `foldPosture` in the System UI Compose library, extracted here to allow for
+ * testing that's not dependent on Compose.
+ */
+@VisibleForTesting
+fun foldPostureInternal(layoutInfo: WindowLayoutInfo?): FoldPosture {
+    return layoutInfo
+        ?.displayFeatures
+        ?.firstNotNullOfOrNull { it as? FoldingFeature }
+        .let { foldingFeature ->
+            when (foldingFeature?.state) {
+                null -> FoldPosture.Folded
+                FoldingFeature.State.HALF_OPENED -> foldingFeature.orientation.toHalfwayPosture()
+                FoldingFeature.State.FLAT ->
+                    if (foldingFeature.isSeparating) {
+                        // Dual screen device.
+                        foldingFeature.orientation.toHalfwayPosture()
+                    } else {
+                        FoldPosture.FullyUnfolded
+                    }
+                else -> error("Unsupported state \"${foldingFeature.state}\"")
+            }
+        }
+}
+
+private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture {
+    return when (this) {
+        FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop
+        FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book
+        else -> error("Unsupported orientation \"$this\"")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2b1cdc2..33dd3d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -647,7 +647,7 @@
         public void dismissKeyguardToLaunch(Intent intentToLaunch) {
             trace("dismissKeyguardToLaunch");
             checkPermission();
-            mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch);
+            Slog.d(TAG, "Ignoring dismissKeyguardToLaunch " + intentToLaunch);
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4e6a872..3009087 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -248,7 +248,6 @@
     private static final int SHOW = 1;
     private static final int HIDE = 2;
     private static final int RESET = 3;
-    private static final int VERIFY_UNLOCK = 4;
     private static final int NOTIFY_FINISHED_GOING_TO_SLEEP = 5;
     private static final int KEYGUARD_DONE = 7;
     private static final int KEYGUARD_DONE_DRAWING = 8;
@@ -2316,15 +2315,6 @@
         mHandler.sendMessage(msg);
     }
 
-    /**
-     * Send message to keyguard telling it to verify unlock
-     * @see #handleVerifyUnlock()
-     */
-    private void verifyUnlockLocked() {
-        if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
-        mHandler.sendEmptyMessage(VERIFY_UNLOCK);
-    }
-
     private void notifyStartedGoingToSleep() {
         if (DEBUG) Log.d(TAG, "notifyStartedGoingToSleep");
         mHandler.sendEmptyMessage(NOTIFY_STARTED_GOING_TO_SLEEP);
@@ -2498,12 +2488,6 @@
                     message = "RESET";
                     handleReset(msg.arg1 != 0);
                     break;
-                case VERIFY_UNLOCK:
-                    message = "VERIFY_UNLOCK";
-                    Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
-                    handleVerifyUnlock();
-                    Trace.endSection();
-                    break;
                 case NOTIFY_STARTED_GOING_TO_SLEEP:
                     message = "NOTIFY_STARTED_GOING_TO_SLEEP";
                     handleNotifyStartedGoingToSleep();
@@ -2720,9 +2704,7 @@
 
     private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
         mUiBgExecutor.execute(() -> {
-            if (DEBUG) {
-                Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
-            }
+            Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
 
             if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                 // Handled in WmLockscreenVisibilityManager if flag is enabled.
@@ -3251,10 +3233,10 @@
         DejankUtils.postAfterTraversal(() -> {
             if (!mPM.isInteractive() && !mPendingLock) {
                 Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal:"
-                        + "mPM.isInteractive()=" + mPM.isInteractive()
-                        + "mPendingLock=" + mPendingLock + "."
-                        + "One of these being false means we re-locked the device during unlock. "
-                        + "Do not proceed to finish keyguard exit and unlock.");
+                        + " mPM.isInteractive()=" + mPM.isInteractive()
+                        + " mPendingLock=" + mPendingLock + "."
+                        + " One of these being false means we re-locked the device during unlock."
+                        + " Do not proceed to finish keyguard exit and unlock.");
                 doKeyguardLocked(null);
                 finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
                 // Ensure WM is notified that we made a decision to show
@@ -3403,7 +3385,7 @@
             }
 
             if (mPowerGestureIntercepted && mOccluded && isSecure()
-                    && mUpdateMonitor.isFaceEnrolled()) {
+                    && mUpdateMonitor.isFaceEnabledAndEnrolled()) {
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
@@ -3437,20 +3419,6 @@
         scheduleNonStrongBiometricIdleTimeout();
     }
 
-    /**
-     * Handle message sent by {@link #verifyUnlock}
-     * @see #VERIFY_UNLOCK
-     */
-    private void handleVerifyUnlock() {
-        Trace.beginSection("KeyguardViewMediator#handleVerifyUnlock");
-        synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
-            setShowingLocked(true);
-            mKeyguardViewControllerLazy.get().dismissAndCollapse();
-        }
-        Trace.endSection();
-    }
-
     private void handleNotifyStartedGoingToSleep() {
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleNotifyStartedGoingToSleep");
@@ -3608,10 +3576,6 @@
         // do nothing
     }
 
-    public void dismissKeyguardToLaunch(Intent intentToLaunch) {
-        // do nothing
-    }
-
     public void onSystemKeyPressed(int keycode) {
         // do nothing
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index f9b89b1..7354cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -18,6 +18,7 @@
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
 import com.android.app.tracing.traceSection
+import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -29,7 +30,7 @@
         screenLifecycle.addObserver(this)
     }
 
-    private val listeners: MutableList<ScreenListener> = mutableListOf()
+    private val listeners: MutableList<ScreenListener> = CopyOnWriteArrayList()
 
     override fun removeCallback(listener: ScreenListener) {
         listeners.remove(listener)
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 c4dfe9a..36412e3 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
@@ -69,7 +69,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
@@ -89,7 +88,7 @@
  */
 interface DeviceEntryFaceAuthRepository {
     /** Provide the current face authentication state for device entry. */
-    val isAuthenticated: Flow<Boolean>
+    val isAuthenticated: StateFlow<Boolean>
 
     /** Whether face auth can run at this point. */
     val canRunFaceAuth: StateFlow<Boolean>
@@ -199,8 +198,7 @@
     private val canRunDetection: StateFlow<Boolean>
 
     private val _isAuthenticated = MutableStateFlow(false)
-    override val isAuthenticated: Flow<Boolean>
-        get() = _isAuthenticated
+    override val isAuthenticated: StateFlow<Boolean> = _isAuthenticated
 
     private var cancellationInProgress = MutableStateFlow(false)
 
@@ -243,61 +241,52 @@
                 .collect(Collectors.toSet())
         dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
 
-        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            canRunFaceAuth =
-                listOf(
-                        *gatingConditionsForAuthAndDetect(),
-                        Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
-                        Pair(
-                            trustRepository.isCurrentUserTrusted.isFalse(),
-                            "currentUserIsNotTrusted"
-                        ),
-                        Pair(
-                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
-                            "isFaceAuthCurrentlyAllowed"
-                        ),
-                        Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
-                    )
-                    .andAllFlows("canFaceAuthRun", faceAuthLog)
-                    .flowOn(mainDispatcher)
-                    .stateIn(applicationScope, SharingStarted.Eagerly, false)
+        canRunFaceAuth =
+            listOf(
+                    *gatingConditionsForAuthAndDetect(),
+                    Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
+                    Pair(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserIsNotTrusted"),
+                    Pair(
+                        biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+                        "isFaceAuthCurrentlyAllowed"
+                    ),
+                    Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
+                )
+                .andAllFlows("canFaceAuthRun", faceAuthLog)
+                .flowOn(backgroundDispatcher)
+                .stateIn(applicationScope, SharingStarted.Eagerly, false)
 
-            // Face detection can run only when lockscreen bypass is enabled
-            // & detection is supported
-            //   & biometric unlock is not allowed
-            //     or user is trusted by trust manager & we want to run face detect to dismiss
-            // keyguard
-            canRunDetection =
-                listOf(
-                        *gatingConditionsForAuthAndDetect(),
-                        Pair(isBypassEnabled, "isBypassEnabled"),
-                        Pair(
-                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed
-                                .isFalse()
-                                .or(trustRepository.isCurrentUserTrusted),
-                            "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
-                        ),
-                        // We don't want to run face detect if fingerprint can be used to unlock the
-                        // device
-                        // but it's not possible to authenticate with FP from the bouncer (UDFPS)
-                        Pair(
-                            and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning)
-                                .isFalse(),
-                            "udfpsAuthIsNotPossibleAnymore"
-                        )
+        // Face detection can run only when lockscreen bypass is enabled
+        // & detection is supported
+        //   & biometric unlock is not allowed
+        //     or user is trusted by trust manager & we want to run face detect to dismiss
+        // keyguard
+        canRunDetection =
+            listOf(
+                    *gatingConditionsForAuthAndDetect(),
+                    Pair(isBypassEnabled, "isBypassEnabled"),
+                    Pair(
+                        biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+                            .isFalse()
+                            .or(trustRepository.isCurrentUserTrusted),
+                        "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
+                    ),
+                    // We don't want to run face detect if fingerprint can be used to unlock the
+                    // device
+                    // but it's not possible to authenticate with FP from the bouncer (UDFPS)
+                    Pair(
+                        and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
+                        "udfpsAuthIsNotPossibleAnymore"
                     )
-                    .andAllFlows("canFaceDetectRun", faceDetectLog)
-                    .flowOn(mainDispatcher)
-                    .stateIn(applicationScope, SharingStarted.Eagerly, false)
-            observeFaceAuthGatingChecks()
-            observeFaceDetectGatingChecks()
-            observeFaceAuthResettingConditions()
-            listenForSchedulingWatchdog()
-            processPendingAuthRequests()
-        } else {
-            canRunFaceAuth = MutableStateFlow(false).asStateFlow()
-            canRunDetection = MutableStateFlow(false).asStateFlow()
-        }
+                )
+                .andAllFlows("canFaceDetectRun", faceDetectLog)
+                .flowOn(backgroundDispatcher)
+                .stateIn(applicationScope, SharingStarted.Eagerly, false)
+        observeFaceAuthGatingChecks()
+        observeFaceDetectGatingChecks()
+        observeFaceAuthResettingConditions()
+        listenForSchedulingWatchdog()
+        processPendingAuthRequests()
     }
 
     private fun listenForSchedulingWatchdog() {
@@ -454,8 +443,8 @@
                 if (errorStatus.isLockoutError()) {
                     _isLockedOut.value = true
                 }
-                _authenticationStatus.value = errorStatus
                 _isAuthenticated.value = false
+                _authenticationStatus.value = errorStatus
                 if (errorStatus.isHardwareError()) {
                     faceAuthLogger.hardwareError(errorStatus)
                     handleFaceHardwareError()
@@ -477,8 +466,17 @@
             }
 
             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
-                _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
+                // Update _isAuthenticated before _authenticationStatus is updated. There are
+                // consumers that receive the face authentication updates through a long chain of
+                // callbacks
+                // _authenticationStatus -> KeyguardUpdateMonitor -> KeyguardStateController ->
+                // onUnlockChanged
+                // These consumers then query the isAuthenticated boolean. This makes sure that the
+                // boolean is updated to new value before the event is propagated.
+                // TODO (b/310592822): once all consumers can use the new system directly, we don't
+                //  have to worry about this ordering.
                 _isAuthenticated.value = true
+                _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
                 faceAuthLogger.faceAuthSuccess(result)
                 onFaceAuthRequestCompleted()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 9bec300..96386f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
@@ -33,11 +34,13 @@
 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates state about device entry fingerprint auth mechanism. */
@@ -74,6 +77,7 @@
     val authController: AuthController,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Application scope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
 ) : DeviceEntryFingerprintAuthRepository {
 
     override val availableFpSensorType: Flow<BiometricType?>
@@ -137,30 +141,34 @@
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val isRunning: Flow<Boolean>
-        get() = conflatedCallbackFlow {
-            val callback =
-                object : KeyguardUpdateMonitorCallback() {
-                    override fun onBiometricRunningStateChanged(
-                        running: Boolean,
-                        biometricSourceType: BiometricSourceType?
-                    ) {
-                        if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
-                            trySendWithFailureLogging(
-                                running,
-                                TAG,
-                                "Fingerprint running state changed"
-                            )
+        get() =
+            conflatedCallbackFlow {
+                    val callback =
+                        object : KeyguardUpdateMonitorCallback() {
+                            override fun onBiometricRunningStateChanged(
+                                running: Boolean,
+                                biometricSourceType: BiometricSourceType?
+                            ) {
+                                if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                                    trySendWithFailureLogging(
+                                        running,
+                                        TAG,
+                                        "Fingerprint running state changed"
+                                    )
+                                }
+                            }
                         }
-                    }
+                    keyguardUpdateMonitor.registerCallback(callback)
+                    trySendWithFailureLogging(
+                        keyguardUpdateMonitor.isFingerprintDetectionRunning,
+                        TAG,
+                        "Initial fingerprint running state"
+                    )
+                    awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
                 }
-            keyguardUpdateMonitor.registerCallback(callback)
-            trySendWithFailureLogging(
-                keyguardUpdateMonitor.isFingerprintDetectionRunning,
-                TAG,
-                "Initial fingerprint running state"
-            )
-            awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
-        }
+                .flowOn(
+                    mainDispatcher
+                ) // keyguardUpdateMonitor requires registration on main thread.
 
     override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
         get() = conflatedCallbackFlow {
@@ -171,7 +179,6 @@
                         biometricSourceType: BiometricSourceType,
                         isStrongBiometric: Boolean,
                     ) {
-
                         sendUpdateIfFingerprint(
                             biometricSourceType,
                             SuccessFingerprintAuthenticationStatus(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
index adb1e01..7c43092 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
@@ -19,11 +19,14 @@
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.DevicePosture
 import com.android.systemui.statusbar.policy.DevicePostureController
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
 
 /** Provide current device posture state. */
 interface DevicePostureRepository {
@@ -34,23 +37,28 @@
 @SysUISingleton
 class DevicePostureRepositoryImpl
 @Inject
-constructor(private val postureController: DevicePostureController) : DevicePostureRepository {
+constructor(
+    private val postureController: DevicePostureController,
+    @Main private val mainDispatcher: CoroutineDispatcher
+) : DevicePostureRepository {
     override val currentDevicePosture: Flow<DevicePosture>
-        get() = conflatedCallbackFlow {
-            val sendPostureUpdate = { posture: Int ->
-                val currentDevicePosture = DevicePosture.toPosture(posture)
-                trySendWithFailureLogging(
-                    currentDevicePosture,
-                    TAG,
-                    "Error sending posture update to $currentDevicePosture"
-                )
-            }
-            val callback = DevicePostureController.Callback { sendPostureUpdate(it) }
-            postureController.addCallback(callback)
-            sendPostureUpdate(postureController.devicePosture)
+        get() =
+            conflatedCallbackFlow {
+                    val sendPostureUpdate = { posture: Int ->
+                        val currentDevicePosture = DevicePosture.toPosture(posture)
+                        trySendWithFailureLogging(
+                            currentDevicePosture,
+                            TAG,
+                            "Error sending posture update to $currentDevicePosture"
+                        )
+                    }
+                    val callback = DevicePostureController.Callback { sendPostureUpdate(it) }
+                    postureController.addCallback(callback)
+                    sendPostureUpdate(postureController.devicePosture)
 
-            awaitClose { postureController.removeCallback(callback) }
-        }
+                    awaitClose { postureController.removeCallback(callback) }
+                }
+                .flowOn(mainDispatcher) // DevicePostureController requirement
 
     companion object {
         const val TAG = "PostureRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index c8cb9e6..f4a74f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -24,6 +24,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.emptyFlow
 
 /**
@@ -34,11 +35,9 @@
  */
 @SysUISingleton
 class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
-    override val isAuthenticated: Flow<Boolean>
-        get() = emptyFlow()
+    override val isAuthenticated: StateFlow<Boolean> = MutableStateFlow(false)
 
-    private val _canRunFaceAuth = MutableStateFlow(false)
-    override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
+    override val canRunFaceAuth: StateFlow<Boolean> = MutableStateFlow(false)
 
     override val authenticationStatus: Flow<FaceAuthenticationStatus>
         get() = emptyFlow()
@@ -46,11 +45,9 @@
     override val detectionStatus: Flow<FaceDetectionStatus>
         get() = emptyFlow()
 
-    private val _isLockedOut = MutableStateFlow(false)
-    override val isLockedOut: StateFlow<Boolean> = _isLockedOut
+    override val isLockedOut: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
 
-    private val _isAuthRunning = MutableStateFlow(false)
-    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+    override val isAuthRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
 
     override val isBypassEnabled: Flow<Boolean>
         get() = emptyFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 85b0f4fb..5ed70b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -44,6 +44,8 @@
     /** Whether face auth is enrolled and enabled for the current user */
     fun isFaceAuthEnabledAndEnrolled(): Boolean
 
+    /** Whether the current user is authenticated successfully with face auth */
+    fun isAuthenticated(): Boolean
     /**
      * Register listener for use from code that cannot use [authenticationStatus] or
      * [detectionStatus]
@@ -53,9 +55,6 @@
     /** Unregister previously registered listener */
     fun unregisterListener(listener: FaceAuthenticationListener)
 
-    /** Whether the face auth interactor is enabled or not. */
-    fun isEnabled(): Boolean
-
     fun onUdfpsSensorTouched()
     fun onAssistantTriggeredOnLockScreen()
     fun onDeviceLifted()
@@ -65,6 +64,9 @@
     fun onPrimaryBouncerUserInput()
     fun onAccessibilityAction()
     fun onWalletLaunched()
+
+    /** Whether face auth is considered class 3 */
+    fun isFaceAuthStrong(): Boolean
 }
 
 /**
@@ -81,4 +83,10 @@
 
     /** Receive status updates whenever face detection runs */
     fun onDetectionStatusChanged(status: FaceDetectionStatus)
+
+    fun onLockoutStateChanged(isLockedOut: Boolean)
+
+    fun onRunningStateChanged(isRunning: Boolean)
+
+    fun onAuthEnrollmentStateChanged(enrolled: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e256b49..e58d771 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -27,12 +27,10 @@
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.shared.model.Position
-import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
@@ -56,6 +54,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -75,17 +74,23 @@
 constructor(
     private val repository: KeyguardRepository,
     private val commandQueue: CommandQueue,
-    private val powerInteractor: PowerInteractor,
-    featureFlags: FeatureFlags,
+    powerInteractor: PowerInteractor,
     sceneContainerFlags: SceneContainerFlags,
     bouncerRepository: KeyguardBouncerRepository,
     configurationRepository: ConfigurationRepository,
     shadeRepository: ShadeRepository,
     sceneInteractorProvider: Provider<SceneInteractor>,
 ) {
-    /** Position information for the shared notification container. */
-    val sharedNotificationContainerPosition =
-        MutableStateFlow(SharedNotificationContainerPosition())
+    // TODO(b/296118689): move to a repository
+    private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds())
+
+    /** Bounds of the notification container. */
+    val notificationContainerBounds: StateFlow<NotificationContainerBounds> =
+        _sharedNotificationContainerBounds.asStateFlow()
+
+    fun setNotificationContainerBounds(position: NotificationContainerBounds) {
+        _sharedNotificationContainerBounds.value = position
+    }
 
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -188,22 +193,18 @@
 
     /** Whether camera is launched over keyguard. */
     val isSecureCameraActive: Flow<Boolean> by lazy {
-        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            combine(
-                    isKeyguardVisible,
-                    primaryBouncerShowing,
-                    onCameraLaunchDetected,
-                ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent ->
-                    when {
-                        isKeyguardVisible -> false
-                        isPrimaryBouncerShowing -> false
-                        else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
-                    }
+        combine(
+                isKeyguardVisible,
+                primaryBouncerShowing,
+                onCameraLaunchDetected,
+            ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent ->
+                when {
+                    isKeyguardVisible -> false
+                    isPrimaryBouncerShowing -> false
+                    else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
                 }
-                .onStart { emit(false) }
-        } else {
-            flowOf(false)
-        }
+            }
+            .onStart { emit(false) }
     }
 
     /** The approximate location on the screen of the fingerprint sensor, if one is available. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 4da48f6..706aba3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -302,4 +302,11 @@
     fun isFinishedInState(state: KeyguardState): Flow<Boolean> {
         return finishedKeyguardState.map { it == state }.distinctUntilChanged()
     }
+
+    /**
+     * Whether we've FINISHED a transition to a state that matches the given predicate. Consider
+     * using [isFinishedInStateWhere] whenever possible instead
+     */
+    fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
+        stateMatcher(finishedKeyguardState.replayCache.last())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index fbadde6..cd6ab31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -42,9 +42,12 @@
 
     override fun isLockedOut(): Boolean = false
 
-    override fun isEnabled() = false
     override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
 
+    override fun isFaceAuthStrong(): Boolean = false
+
+    override fun isAuthenticated(): Boolean = false
+
     override fun registerListener(listener: FaceAuthenticationListener) {}
 
     override fun unregisterListener(listener: FaceAuthenticationListener) {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 2641846..ae356cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -30,8 +30,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -45,6 +43,7 @@
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -71,10 +70,9 @@
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val repository: DeviceEntryFaceAuthRepository,
-    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val featureFlags: FeatureFlags,
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
@@ -88,16 +86,16 @@
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
 
     override fun start() {
-        if (!isEnabled()) {
-            return
-        }
-        // This is required because fingerprint state required for the face auth repository is
-        // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
-        // state which makes lazy injection not an option.
+        // Todo(b/310594096): there is a dependency cycle introduced by the repository depending on
+        //  KeyguardBypassController, which in turn depends on KeyguardUpdateMonitor through
+        //  its other dependencies. Once bypassEnabled state is available through a repository, we
+        //  can break that cycle and inject this interactor directly into KeyguardUpdateMonitor
         keyguardUpdateMonitor.setFaceAuthInteractor(this)
         observeFaceAuthStateUpdates()
         faceAuthenticationLogger.interactorStarted()
-        primaryBouncerInteractor.isShowing
+        primaryBouncerInteractor
+            .get()
+            .isShowing
             .whenItFlipsToTrue()
             .onEach {
                 faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -176,7 +174,7 @@
                         FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
                         // Fallback to detection if bouncer is not showing so that we can detect a
                         // face and then show the bouncer to the user if face auth can't run
-                        fallbackToDetect = !primaryBouncerInteractor.isBouncerShowing()
+                        fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
                     )
                 }
             }
@@ -231,9 +229,8 @@
 
     override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
 
-    override fun isEnabled(): Boolean {
-        return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
-    }
+    override fun isFaceAuthStrong(): Boolean =
+        facePropertyRepository.sensorInfo.value?.strength == SensorStrength.STRONG
 
     override fun onPrimaryBouncerUserInput() {
         repository.cancel()
@@ -248,29 +245,24 @@
     override val detectionStatus = repository.detectionStatus
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
-        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            if (repository.isLockedOut.value) {
-                faceAuthenticationStatusOverride.value =
-                    ErrorFaceAuthenticationStatus(
-                        BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
-                        context.resources.getString(R.string.keyguard_face_unlock_unavailable)
-                    )
-            } else {
-                faceAuthenticationStatusOverride.value = null
-                faceAuthenticationLogger.authRequested(uiEvent)
-                repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect)
-            }
+        if (repository.isLockedOut.value) {
+            faceAuthenticationStatusOverride.value =
+                ErrorFaceAuthenticationStatus(
+                    BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
+                    context.resources.getString(R.string.keyguard_face_unlock_unavailable)
+                )
         } else {
-            faceAuthenticationLogger.ignoredFaceAuthTrigger(
-                uiEvent,
-                ignoredReason = "Skipping face auth request because feature flag is false"
-            )
+            faceAuthenticationStatusOverride.value = null
+            faceAuthenticationLogger.authRequested(uiEvent)
+            repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect)
         }
     }
 
     override fun isFaceAuthEnabledAndEnrolled(): Boolean =
         biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value
 
+    override fun isAuthenticated(): Boolean = repository.isAuthenticated.value
+
     private fun observeFaceAuthStateUpdates() {
         authenticationStatus
             .onEach { authStatusUpdate ->
@@ -284,6 +276,21 @@
             }
             .flowOn(mainDispatcher)
             .launchIn(applicationScope)
+        repository.isLockedOut
+            .onEach { lockedOut -> listeners.forEach { it.onLockoutStateChanged(lockedOut) } }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+        repository.isAuthRunning
+            .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+
+        biometricSettingsRepository.isFaceAuthEnrolledAndEnabled
+            .onEach { enrolledAndEnabled ->
+                listeners.forEach { it.onAuthEnrollmentStateChanged(enrolledAndEnabled) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt
new file mode 100644
index 0000000..3540a0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui
+
+import android.content.Context
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
+import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
+import javax.inject.Inject
+
+/** A class to detect when a user swipes up anywhere on the display. */
+@SysUISingleton
+class SwipeUpAnywhereGestureHandler
+@Inject
+constructor(
+    context: Context,
+    displayTracker: DisplayTracker,
+    logger: SwipeUpGestureLogger,
+) :
+    SwipeUpGestureHandler(
+        context,
+        displayTracker,
+        logger,
+        loggerTag = "SwipeUpAnywhereGestureHandler"
+    ) {
+    override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
+        return true
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index cb5813e..594865d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -20,52 +20,21 @@
 import android.view.ViewGroup
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.classifier.Classifier
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.scrim.ScrimView
-import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.statusbar.NotificationShadeWindowController
-import javax.inject.Inject
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class AlternateBouncerBinder
-@Inject
-constructor(
-    private val notificationShadeWindowView: NotificationShadeWindowView,
-    private val alternateBouncerViewModel: AlternateBouncerViewModel,
-    @Application private val scope: CoroutineScope,
-    private val notificationShadeWindowController: NotificationShadeWindowController,
-) : CoreStartable {
-    override fun start() {
-        if (!DeviceEntryUdfpsRefactor.isEnabled) {
-            return
-        }
-
-        AlternateBouncerViewBinder.bind(
-            notificationShadeWindowView.requireViewById(R.id.alternate_bouncer),
-            alternateBouncerViewModel,
-            scope,
-            notificationShadeWindowController,
-        )
-    }
-}
-
-/**
- * Binds the alternate bouncer view to its view-model.
- *
- * To use this properly, users should maintain a one-to-one relationship between the [View] and the
- * view-binding, binding each view only once. It is okay and expected for the same instance of the
- * view-model to be reused for multiple view/view-binder bindings.
- */
+/** Binds the alternate bouncer view to its view-model. */
 @ExperimentalCoroutinesApi
 object AlternateBouncerViewBinder {
 
@@ -76,6 +45,9 @@
         viewModel: AlternateBouncerViewModel,
         scope: CoroutineScope,
         notificationShadeWindowController: NotificationShadeWindowController,
+        falsingManager: FalsingManager,
+        swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
+        tapGestureDetector: TapGestureDetector,
     ) {
         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
         scope.launch {
@@ -93,9 +65,25 @@
                 scrim.viewAlpha = 0f
 
                 launch {
-                    viewModel.onClickListener.collect {
-                        // TODO (b/287599719): Support swiping to dismiss altBouncer
-                        alternateBouncerViewContainer.setOnClickListener(it)
+                    viewModel.registerForDismissGestures.collect { registerForDismissGestures ->
+                        if (registerForDismissGestures) {
+                            swipeUpAnywhereGestureHandler.addOnGestureDetectedCallback(swipeTag) { _
+                                ->
+                                if (
+                                    !falsingManager.isFalseTouch(Classifier.ALTERNATE_BOUNCER_SWIPE)
+                                ) {
+                                    viewModel.showPrimaryBouncer()
+                                }
+                            }
+                            tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ ->
+                                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                                    viewModel.showPrimaryBouncer()
+                                }
+                            }
+                        } else {
+                            swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback(swipeTag)
+                            tapGestureDetector.removeOnGestureDetectedCallback(tapTag)
+                        }
                     }
                 }
 
@@ -112,3 +100,6 @@
         }
     }
 }
+
+private const val swipeTag = "AlternateBouncer-SWIPE"
+private const val tapTag = "AlternateBouncer-TAP"
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 c5a8375..eee5206 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
@@ -17,13 +17,11 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.annotation.SuppressLint
-import android.content.Intent
 import android.graphics.Rect
 import android.graphics.drawable.Animatable2
 import android.util.Size
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewPropertyAnimator
 import android.widget.ImageView
 import androidx.core.animation.CycleInterpolator
 import androidx.core.animation.ObjectAnimator
@@ -34,7 +32,6 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.view.LaunchableLinearLayout
@@ -43,9 +40,12 @@
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
+import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.doOnEnd
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -79,7 +79,7 @@
      * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
      * it is bound.
      */
-    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     @Deprecated("Deprecated as part of b/278057014")
     interface Binding {
         /** Notifies that device configuration has changed. */
@@ -133,8 +133,7 @@
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
-
-                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+                    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.startButton.collect { buttonModel ->
                             updateButton(
@@ -147,7 +146,7 @@
                         }
                     }
 
-                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+                    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.endButton.collect { buttonModel ->
                             updateButton(
@@ -185,7 +184,7 @@
                         }
                     }
 
-                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+                    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = startButton,
@@ -194,7 +193,7 @@
                         )
                     }
 
-                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+                    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = endButton,
@@ -220,7 +219,7 @@
                             }
                     }
 
-                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+                    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         configurationBasedDimensions.collect { dimensions ->
                             startButton.updateLayoutParams<ViewGroup.LayoutParams> {
@@ -378,13 +377,14 @@
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             if (viewModel.useLongPress) {
-                val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
-                    view,
-                    viewModel,
-                    messageDisplayer,
-                    vibratorHelper,
-                    falsingManager,
-                )
+                val onTouchListener =
+                    KeyguardQuickAffordanceOnTouchListener(
+                        view,
+                        viewModel,
+                        messageDisplayer,
+                        vibratorHelper,
+                        falsingManager,
+                    )
                 view.setOnTouchListener(onTouchListener)
                 view.setOnClickListener {
                     messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
@@ -403,9 +403,7 @@
                         KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
                     shakeAnimator.interpolator =
                         CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
-                    shakeAnimator.doOnEnd {
-                        view.translationX = 0f
-                    }
+                    shakeAnimator.doOnEnd { view.translationX = 0f }
                     shakeAnimator.start()
 
                     vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
@@ -425,7 +423,7 @@
     }
 
     @Deprecated("Deprecated as part of b/278057014")
-    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private suspend fun updateButtonAlpha(
         view: View,
         viewModel: Flow<KeyguardQuickAffordanceViewModel>,
@@ -456,7 +454,7 @@
     }
 
     @Deprecated("Deprecated as part of b/278057014")
-    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnLongClickListener(
         private val falsingManager: FalsingManager?,
         private val viewModel: KeyguardQuickAffordanceViewModel,
@@ -493,7 +491,7 @@
     }
 
     @Deprecated("Deprecated as part of b/278057014")
-    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnClickListener(
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val falsingManager: FalsingManager,
@@ -535,13 +533,7 @@
         view: View,
     ) {
         activityStarter.postStartActivityDismissingKeyguard(
-            Intent(Intent.ACTION_SET_WALLPAPER).apply {
-                flags = Intent.FLAG_ACTIVITY_NEW_TASK
-                view.context
-                    .getString(R.string.config_wallpaperPickerPackage)
-                    .takeIf { it.isNotEmpty() }
-                    ?.let { packageName -> setPackage(packageName) }
-            },
+            WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
             /* delay= */ 0,
             /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
             /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
@@ -549,7 +541,7 @@
     }
 
     @Deprecated("Deprecated as part of b/278057014")
-    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private data class ConfigurationBasedDimensions(
         val defaultBurnInPreventionYOffsetPx: Int,
         val buttonSizePx: Size,
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 4de9fc3..c0d3d33 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
@@ -34,13 +34,13 @@
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -308,7 +308,7 @@
     private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) :
         OnLayoutChangeListener {
         override fun onLayoutChange(
-            v: View,
+            view: View,
             left: Int,
             top: Int,
             right: Int,
@@ -318,18 +318,16 @@
             oldRight: Int,
             oldBottom: Int
         ) {
-            val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View?
-            if (nsslPlaceholder != null) {
+            view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder ->
                 // After layout, ensure the notifications are positioned correctly
-                viewModel.onSharedNotificationContainerPositionChanged(
-                    nsslPlaceholder.top.toFloat(),
-                    nsslPlaceholder.bottom.toFloat(),
+                viewModel.onNotificationContainerBoundsChanged(
+                    notificationListPlaceholder.top.toFloat(),
+                    notificationListPlaceholder.bottom.toFloat(),
                 )
             }
 
-            val ksv = v.findViewById(R.id.keyguard_status_view) as View?
-            if (ksv != null) {
-                viewModel.statusViewTop = ksv.top
+            view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
+                viewModel.statusViewTop = statusView.top
             }
         }
     }
@@ -384,7 +382,7 @@
                 }
                 visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
             }
-            featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
+            newAodTransition() -> {
                 animateInIconTranslation(statusViewMigrated)
                 if (isVisible.value) {
                     CrossFadeHelper.fadeIn(this, animatorListener)
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 6beef8e..8514225 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
@@ -17,19 +17,20 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.content.Intent
 import android.view.View
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 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.KeyguardSettingsMenuViewModel
+import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
+import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -98,13 +99,7 @@
         view: View,
     ) {
         activityStarter.postStartActivityDismissingKeyguard(
-            Intent(Intent.ACTION_SET_WALLPAPER).apply {
-                flags = Intent.FLAG_ACTIVITY_NEW_TASK
-                view.context
-                    .getString(R.string.config_wallpaperPickerPackage)
-                    .takeIf { it.isNotEmpty() }
-                    ?.let { packageName -> setPackage(packageName) }
-            },
+            WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
             /* delay= */ 0,
             /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
             /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
@@ -127,5 +122,4 @@
             }
             .start()
     }
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 0cf891c..fa27442 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -33,7 +33,6 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Named
@@ -50,7 +49,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
+    defaultDeviceEntrySection: DefaultDeviceEntrySection,
     defaultShortcutsSection: DefaultShortcutsSection,
     @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
     defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
@@ -62,14 +61,13 @@
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
     clockSection: ClockSection,
-    smartspaceSection: SmartspaceSection
+    smartspaceSection: SmartspaceSection,
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
     override val sections =
         listOfNotNull(
             defaultIndicationAreaSection,
-            defaultDeviceEntryIconSection,
             defaultShortcutsSection,
             defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
@@ -80,7 +78,8 @@
             aodBurnInSection,
             communalTutorialIndicatorSection,
             clockSection,
-            smartspaceSection
+            smartspaceSection,
+            defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 14e8f89..bf70682 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -42,7 +42,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
+    defaultDeviceEntrySection: DefaultDeviceEntrySection,
     @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
     defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
@@ -59,7 +59,6 @@
     override val sections =
         listOfNotNull(
             defaultIndicationAreaSection,
-            defaultDeviceEntryIconSection,
             defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
             alignShortcutsToUdfpsSection,
@@ -69,6 +68,7 @@
             splitShadeGuidelines,
             aodNotificationIconsSection,
             aodBurnInSection,
+            defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
         )
 
     companion object {
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 0d397bf..f890ae6 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
@@ -23,7 +23,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
@@ -47,7 +47,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
+    defaultDeviceEntrySection: DefaultDeviceEntrySection,
     defaultShortcutsSection: DefaultShortcutsSection,
     @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
     defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
@@ -65,7 +65,6 @@
     override val sections =
         listOfNotNull(
             defaultIndicationAreaSection,
-            defaultDeviceEntryIconSection,
             defaultShortcutsSection,
             defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
@@ -76,6 +75,7 @@
             aodNotificationIconsSection,
             aodBurnInSection,
             communalTutorialIndicatorSection,
+            defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 790ddd5..77ab9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -23,6 +23,7 @@
 import android.util.DisplayMetrics
 import android.view.View
 import android.view.WindowManager
+import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -31,24 +32,32 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder
 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 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.shade.NotificationPanelView
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+/** Includes both the device entry icon and the alternate bouncer scrim. */
 @ExperimentalCoroutinesApi
-class DefaultDeviceEntryIconSection
+class DefaultDeviceEntrySection
 @Inject
 constructor(
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -62,14 +71,26 @@
     private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
     private val falsingManager: Lazy<FalsingManager>,
+    private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>,
+    private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>,
+    @Application private val scope: CoroutineScope,
+    private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
+    private val tapGestureDetector: Lazy<TapGestureDetector>,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
+    private val alternateBouncerViewId = R.id.alternate_bouncer
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
             return
         }
 
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            // The alternate bouncer scrim needs to be below the device entry icon view, so
+            // we add the view here before adding the device entry icon view.
+            View.inflate(context, R.layout.alternate_bouncer, constraintLayout)
+        }
+
         notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
             notificationPanelView.removeView(it)
         }
@@ -95,6 +116,17 @@
                     falsingManager.get(),
                 )
             }
+            constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
+                AlternateBouncerViewBinder.bind(
+                    it,
+                    alternateBouncerViewModel.get(),
+                    scope,
+                    notificationShadeWindowController.get(),
+                    falsingManager.get(),
+                    swipeUpAnywhereGestureHandler.get(),
+                    tapGestureDetector.get(),
+                )
+            }
         } else {
             constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
                 lockIconViewController.get().setLockIconView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 7512e51..0588857 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -30,10 +30,13 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 
@@ -43,18 +46,24 @@
 constructor(
     context: Context,
     private val featureFlags: FeatureFlags,
+    sceneContainerFlags: SceneContainerFlags,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+    ambientState: AmbientState,
     controller: NotificationStackScrollLayoutController,
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) :
     NotificationStackScrollLayoutSection(
         context,
+        sceneContainerFlags,
         notificationPanelView,
         sharedNotificationContainer,
         sharedNotificationContainerViewModel,
+        notificationStackAppearanceViewModel,
+        ambientState,
         controller,
         notificationStackSizeCalculator,
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
index 37c00b6..a65149e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
@@ -25,6 +25,7 @@
 @Module
 abstract class KeyguardSectionsModule {
 
+    @Module
     companion object {
         const val KEYGUARD_AMBIENT_INDICATION_AREA_SECTION =
                 "keyguard_ambient_indication_area_section"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 441f59d..a9e766e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -24,20 +24,28 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import kotlinx.coroutines.DisposableHandle
 
 abstract class NotificationStackScrollLayoutSection
 constructor(
     protected val context: Context,
+    private val sceneContainerFlags: SceneContainerFlags,
     private val notificationPanelView: NotificationPanelView,
     private val sharedNotificationContainer: SharedNotificationContainer,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+    private val ambientState: AmbientState,
     private val controller: NotificationStackScrollLayoutController,
     private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
 ) : KeyguardSection() {
@@ -68,9 +76,19 @@
             SharedNotificationContainerBinder.bind(
                 sharedNotificationContainer,
                 sharedNotificationContainerViewModel,
+                sceneContainerFlags,
                 controller,
                 notificationStackSizeCalculator,
             )
+        if (sceneContainerFlags.flexiNotifsEnabled()) {
+            NotificationStackAppearanceViewBinder.bind(
+                sharedNotificationContainer,
+                notificationStackAppearanceViewModel,
+                sceneContainerFlags,
+                ambientState,
+                controller,
+            )
+        }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
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 f2559ba..05ef5c3 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
@@ -30,10 +30,13 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 
@@ -43,18 +46,24 @@
 constructor(
     context: Context,
     private val featureFlags: FeatureFlags,
+    sceneContainerFlags: SceneContainerFlags,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+    ambientState: AmbientState,
     controller: NotificationStackScrollLayoutController,
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) :
     NotificationStackScrollLayoutSection(
         context,
+        sceneContainerFlags,
         notificationPanelView,
         sharedNotificationContainer,
         sharedNotificationContainerViewModel,
+        notificationStackAppearanceViewModel,
+        ambientState,
         controller,
         notificationStackSizeCalculator,
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 235a28d..bb7bcd9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -18,12 +18,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Color
-import android.view.View
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
@@ -38,9 +36,8 @@
 class AlternateBouncerViewModel
 @Inject
 constructor(
-    statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     transitionInteractor: KeyguardTransitionInteractor,
-    falsingManager: FalsingManager,
 ) {
     // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
     private val alternateBouncerScrimAlpha = .66f
@@ -83,21 +80,10 @@
     /** An observable for the scrim color. Change color for easier debugging. */
     val scrimColor: Flow<Int> = flowOf(Color.BLACK)
 
-    private val clickListener =
-        View.OnClickListener {
-            if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
-            }
-        }
+    val registerForDismissGestures: Flow<Boolean> =
+        transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged()
 
-    val onClickListener: Flow<View.OnClickListener?> =
-        transitionToAlternateBouncerProgress
-            .map {
-                if (it == 1f) {
-                    clickListener
-                } else {
-                    null
-                }
-            }
-            .distinctUntilChanged()
+    fun showPrimaryBouncer() {
+        statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 99529a1..9a50d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -49,12 +50,22 @@
         transitionInteractor.startedKeyguardState.map { keyguardState ->
             keyguardState == KeyguardState.AOD
         }
+
+    private fun getColor(usingBackgroundProtection: Boolean): Int {
+        return if (usingBackgroundProtection) {
+            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+        } else {
+            Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+        }
+    }
+
     private val color: Flow<Int> =
-        configurationRepository.onAnyConfigurationChange
-            .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
-            .onStart {
-                emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
-            }
+        deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
+            configurationRepository.onAnyConfigurationChange
+                .map { getColor(useBgProtection) }
+                .onStart { emit(getColor(useBgProtection)) }
+        }
+
     private val useAodIconVariant: Flow<Boolean> =
         combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) {
                 isTransitionToAod,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 60f75f0..524fa1e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -21,11 +21,10 @@
 import android.util.MathUtils
 import android.view.View.VISIBLE
 import com.android.app.animation.Interpolators
-import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
+import com.android.systemui.Flags.newAodTransition
+import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -49,6 +48,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -66,7 +66,6 @@
     private val context: Context,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
-    private val featureFlags: FeatureFlagsClassic,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
@@ -99,6 +98,10 @@
 
     val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
 
+    /** the shared notification container bounds *on the lockscreen* */
+    val notificationBounds: StateFlow<NotificationContainerBounds> =
+        keyguardInteractor.notificationContainerBounds
+
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
         previewMode.flatMapLatest {
@@ -244,13 +247,13 @@
         previewMode.value = PreviewMode(true)
     }
 
-    fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+    fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) {
         // Notifications should not be visible in preview mode
         if (previewMode.value.isInPreviewMode) {
             return
         }
-        keyguardInteractor.sharedNotificationContainerPosition.value =
-            SharedNotificationContainerPosition(top, bottom)
+
+        keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom))
     }
 
     /** Is there an expanded pulse, are we animating in response? */
@@ -280,7 +283,7 @@
                         dozeParameters.displayNeedsBlanking -> false
                         // We only want the appear animations to happen when the notifications
                         // get fully hidden, since otherwise the un-hide animation overlaps.
-                        featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
+                        newAodTransition() -> true
                         else -> fullyHidden
                     }
                 AnimatableEvent(fullyHidden, animate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index c03e4d9..539db7f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -37,6 +38,8 @@
     deviceEntryInteractor: DeviceEntryInteractor,
     communalInteractor: CommunalInteractor,
     val longPress: KeyguardLongPressViewModel,
+    val keyguardRoot: KeyguardRootViewModel,
+    val notifications: NotificationsPlaceholderViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt b/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt
new file mode 100644
index 0000000..84e0566
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.util
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.res.R
+
+/** Provides function(s) to get intent for launching the Wallpaper Picker app. */
+object WallpaperPickerIntentUtils {
+
+    fun getIntent(context: Context, launchSource: String): Intent {
+        return Intent(Intent.ACTION_SET_WALLPAPER).apply {
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            context
+                .getString(R.string.config_wallpaperPickerPackage)
+                .takeIf { it.isNotEmpty() }
+                ?.let { packageName -> setPackage(packageName) }
+            putExtra(WALLPAPER_LAUNCH_SOURCE, launchSource)
+        }
+    }
+
+    private const val WALLPAPER_LAUNCH_SOURCE = "com.android.wallpaper.LAUNCH_SOURCE"
+    const val LAUNCH_SOURCE_KEYGUARD = "app_launched_keyguard"
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt
similarity index 69%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt
index 8e55695..346f269 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt
@@ -14,7 +14,12 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.log.dagger
 
-import org.robolectric.annotation.GraphicsMode;
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardMediaController. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardMediaControllerLog
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 0d81940..0b3bbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -156,6 +156,14 @@
         return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for all logs related to keyguard media controller. */
+    @Provides
+    @SysUISingleton
+    @KeyguardMediaControllerLog
+    public static LogBuffer provideKeyguardMediaControllerLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyguardMediaControllerLog", 50 /* maxSize */, false /* systrace */);
+    }
+
     /** Provides a logging buffer for all logs related to unseen notifications. */
     @Provides
     @SysUISingleton
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 773c292..945bf9a 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
@@ -23,7 +23,6 @@
 import android.os.Handler
 import android.os.UserHandle
 import android.provider.Settings
-import android.util.Log
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
@@ -63,22 +62,21 @@
     @Main private val handler: Handler,
     configurationController: ConfigurationController,
     private val splitShadeStateController: SplitShadeStateController,
+    private val logger: KeyguardMediaControllerLogger,
     dumpManager: DumpManager,
 ) : Dumpable {
-    /** It's added for debugging purpose to directly see last received StatusBarState. */
-    private var lastReceivedStatusBarState = -1
+    private var lastUsedStatusBarState = -1
 
     init {
         dumpManager.registerDumpable(this)
         statusBarStateController.addCallback(
             object : StatusBarStateController.StateListener {
                 override fun onStateChanged(newState: Int) {
-                    lastReceivedStatusBarState = newState
-                    refreshMediaPosition()
+                    refreshMediaPosition(reason = "StatusBarState.onStateChanged")
                 }
 
                 override fun onDozingChanged(isDozing: Boolean) {
-                    refreshMediaPosition()
+                    refreshMediaPosition(reason = "StatusBarState.onDozingChanged")
                 }
             }
         )
@@ -100,7 +98,7 @@
                                 true,
                                 UserHandle.USER_CURRENT
                             )
-                        refreshMediaPosition()
+                        refreshMediaPosition(reason = "allowMediaPlayerOnLockScreen changed")
                     }
                 }
             }
@@ -132,7 +130,7 @@
             }
             field = value
             reattachHostView()
-            refreshMediaPosition()
+            refreshMediaPosition(reason = "useSplitShade changed")
         }
 
     /** Is the media player visible? */
@@ -147,7 +145,7 @@
     var isDozeWakeUpAnimationWaiting: Boolean = false
         set(value) {
             field = value
-            refreshMediaPosition()
+            refreshMediaPosition(reason = "isDozeWakeUpAnimationWaiting changed")
         }
 
     /** single pane media container placed at the top of the notifications list */
@@ -181,7 +179,7 @@
 
     /** Called whenever the media hosts visibility changes */
     private fun onMediaHostVisibilityChanged(visible: Boolean) {
-        refreshMediaPosition()
+        refreshMediaPosition(reason = "onMediaHostVisibilityChanged")
         if (visible) {
             mediaHost.hostView.layoutParams.apply {
                 height = ViewGroup.LayoutParams.WRAP_CONTENT
@@ -194,7 +192,7 @@
     fun attachSplitShadeContainer(container: ViewGroup) {
         splitShadeContainer = container
         reattachHostView()
-        refreshMediaPosition()
+        refreshMediaPosition(reason = "attachSplitShadeContainer")
     }
 
     private fun reattachHostView() {
@@ -217,30 +215,41 @@
         }
     }
 
-    fun refreshMediaPosition() {
+    fun refreshMediaPosition(reason: String) {
         val currentState = statusBarStateController.state
-        if (lastReceivedStatusBarState != -1 && currentState != lastReceivedStatusBarState) {
-            Log.wtfStack(
-                TAG,
-                "currentState[${StatusBarState.toString(currentState)}] is " +
-                    "different from the last " +
-                    "received one[${StatusBarState.toString(lastReceivedStatusBarState)}]."
-            )
-        }
 
         val keyguardOrUserSwitcher = (currentState == StatusBarState.KEYGUARD)
         // mediaHost.visible required for proper animations handling
+        val isMediaHostVisible = mediaHost.visible
+        val isBypassNotEnabled = !bypassController.bypassEnabled
+        val currentAllowMediaPlayerOnLockScreen = allowMediaPlayerOnLockScreen
+        val useSplitShade = useSplitShade
+        val shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade()
+
         visible =
-            mediaHost.visible &&
-                !bypassController.bypassEnabled &&
+            isMediaHostVisible &&
+                isBypassNotEnabled &&
                 keyguardOrUserSwitcher &&
-                allowMediaPlayerOnLockScreen &&
-                shouldBeVisibleForSplitShade()
+                currentAllowMediaPlayerOnLockScreen &&
+                shouldBeVisibleForSplitShade
         if (visible) {
             showMediaPlayer()
         } else {
             hideMediaPlayer()
         }
+        logger.logRefreshMediaPosition(
+            reason = reason,
+            visible = visible,
+            useSplitShade = useSplitShade,
+            currentState = currentState,
+            keyguardOrUserSwitcher = keyguardOrUserSwitcher,
+            mediaHostVisible = isMediaHostVisible,
+            bypassNotEnabled = isBypassNotEnabled,
+            currentAllowMediaPlayerOnLockScreen = currentAllowMediaPlayerOnLockScreen,
+            shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade
+        )
+
+        lastUsedStatusBarState = currentState
     }
 
     private fun shouldBeVisibleForSplitShade(): Boolean {
@@ -298,10 +307,10 @@
                 println("isDozeWakeUpAnimationWaiting", isDozeWakeUpAnimationWaiting)
                 println("singlePaneContainer", singlePaneContainer)
                 println("splitShadeContainer", splitShadeContainer)
-                if (lastReceivedStatusBarState != -1) {
+                if (lastUsedStatusBarState != -1) {
                     println(
-                        "lastReceivedStatusBarState",
-                        StatusBarState.toString(lastReceivedStatusBarState)
+                        "lastUsedStatusBarState",
+                        StatusBarState.toString(lastUsedStatusBarState)
                     )
                 }
                 println(
@@ -311,8 +320,4 @@
             }
         }
     }
-
-    private companion object {
-        private const val TAG = "KeyguardMediaController"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
new file mode 100644
index 0000000..41fef88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.dagger.KeyguardMediaControllerLog
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/** Logger class for [KeyguardMediaController]. */
+open class KeyguardMediaControllerLogger
+@Inject
+constructor(@KeyguardMediaControllerLog private val logBuffer: LogBuffer) {
+
+    fun logRefreshMediaPosition(
+        reason: String,
+        visible: Boolean,
+        useSplitShade: Boolean,
+        currentState: Int,
+        keyguardOrUserSwitcher: Boolean,
+        mediaHostVisible: Boolean,
+        bypassNotEnabled: Boolean,
+        currentAllowMediaPlayerOnLockScreen: Boolean,
+        shouldBeVisibleForSplitShade: Boolean
+    ) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = reason
+                bool1 = visible
+                bool2 = useSplitShade
+                int1 = currentState
+                bool3 = keyguardOrUserSwitcher
+                bool4 = mediaHostVisible
+                int2 = if (bypassNotEnabled) 1 else 0
+                str2 = currentAllowMediaPlayerOnLockScreen.toString()
+                str3 = shouldBeVisibleForSplitShade.toString()
+            },
+            {
+                "refreshMediaPosition(reason=$str1, " +
+                    "currentState=${StatusBarState.toString(int1)}, " +
+                    "visible=$bool1, useSplitShade=$bool2, " +
+                    "keyguardOrUserSwitcher=$bool3, " +
+                    "mediaHostVisible=$bool4, " +
+                    "bypassNotEnabled=${int2 == 1}, " +
+                    "currentAllowMediaPlayerOnLockScreen=$str2, " +
+                    "shouldBeVisibleForSplitShade=$str3)"
+            }
+        )
+
+    private companion object {
+        private const val TAG = "KeyguardMediaControllerLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index f2db088..44232ff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -19,12 +19,18 @@
 import android.app.StatusBarManager
 import android.os.UserHandle
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import javax.inject.Inject
 
 @SysUISingleton
-class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+class MediaFlags
+@Inject
+constructor(
+    private val featureFlags: FeatureFlagsClassic,
+    private val sceneContainerFlags: SceneContainerFlags
+) {
     /**
      * Check whether media control actions should be based on PlaybackState instead of notification
      */
@@ -48,4 +54,8 @@
 
     /** Check whether we allow remote media to generate resume controls */
     fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+
+    /** Check whether to use flexiglass layout */
+    fun isFlexiglassEnabled() =
+        sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
copy to packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
index 44387c2..898298c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -14,26 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shared
+package com.android.systemui.media.controls.util
 
 import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
-/** Helper for reading or using the notifications live data store refactor flag state. */
+/** Helper for reading or using the media_in_scene_container flag state. */
 @Suppress("NOTHING_TO_INLINE")
-object NotificationsLiveDataStoreRefactor {
+object MediaInSceneContainerFlag {
     /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR
+    const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
 
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
+    /** Is the flag enabled? */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationsLiveDataStoreRefactor()
+        get() = Flags.mediaInSceneContainer()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -50,4 +45,4 @@
      */
     @JvmStatic
     inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 0c5a14f..48f432e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -58,8 +58,8 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.concurrent.Executor;
@@ -85,6 +85,13 @@
     final MediaOutputController mMediaOutputController;
     final BroadcastSender mBroadcastSender;
 
+    /**
+     * Signals whether the dialog should NOT show app-related metadata.
+     *
+     * <p>A metadata-less dialog hides the title, subtitle, and app icon in the header.
+     */
+    private final boolean mIncludePlaybackAndAppMetadata;
+
     @VisibleForTesting
     View mDialogView;
     private TextView mHeaderTitle;
@@ -210,8 +217,11 @@
         }
     }
 
-    public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender,
-            MediaOutputController mediaOutputController) {
+    public MediaOutputBaseDialog(
+            Context context,
+            BroadcastSender broadcastSender,
+            MediaOutputController mediaOutputController,
+            boolean includePlaybackAndAppMetadata) {
         super(context, R.style.Theme_SystemUI_Dialog_Media);
 
         // Save the context that is wrapped with our theme.
@@ -226,6 +236,7 @@
         mListPaddingTop = mContext.getResources().getDimensionPixelSize(
                 R.dimen.media_output_dialog_list_padding_top);
         mExecutor = Executors.newSingleThreadExecutor();
+        mIncludePlaybackAndAppMetadata = includePlaybackAndAppMetadata;
     }
 
     @Override
@@ -354,7 +365,10 @@
             updateDialogBackgroundColor();
             mHeaderIcon.setVisibility(View.GONE);
         }
-        if (appSourceIcon != null) {
+
+        if (!mIncludePlaybackAndAppMetadata) {
+            mAppResourceIcon.setVisibility(View.GONE);
+        } else if (appSourceIcon != null) {
             Icon appIcon = appSourceIcon.toIcon(mContext);
             mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
             mAppResourceIcon.setImageIcon(appIcon);
@@ -373,17 +387,24 @@
             mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
         }
         mAppButton.setText(mMediaOutputController.getAppSourceName());
-        // Update title and subtitle
-        mHeaderTitle.setText(getHeaderText());
-        final CharSequence subTitle = getHeaderSubtitle();
-        if (TextUtils.isEmpty(subTitle)) {
+
+        if (!mIncludePlaybackAndAppMetadata) {
+            mHeaderTitle.setVisibility(View.GONE);
             mHeaderSubtitle.setVisibility(View.GONE);
-            mHeaderTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
         } else {
-            mHeaderSubtitle.setVisibility(View.VISIBLE);
-            mHeaderSubtitle.setText(subTitle);
-            mHeaderTitle.setGravity(Gravity.NO_GRAVITY);
+            // Update title and subtitle
+            mHeaderTitle.setText(getHeaderText());
+            final CharSequence subTitle = getHeaderSubtitle();
+            if (TextUtils.isEmpty(subTitle)) {
+                mHeaderSubtitle.setVisibility(View.GONE);
+                mHeaderTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+            } else {
+                mHeaderSubtitle.setVisibility(View.VISIBLE);
+                mHeaderSubtitle.setText(subTitle);
+                mHeaderTitle.setGravity(Gravity.NO_GRAVITY);
+            }
         }
+
         // Show when remote media session is available or
         //      when the device supports BT LE audio + media is playing
         mStopButton.setVisibility(getStopButtonVisibility());
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index ac64300..8e0191e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -42,12 +42,10 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.media.BluetoothMediaDevice;
-import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.qrcode.QrCodeGenerator;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import com.google.zxing.WriterException;
@@ -237,7 +235,11 @@
 
     MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
             BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
-        super(context, broadcastSender, mediaOutputController);
+        super(
+                context,
+                broadcastSender,
+                mediaOutputController, /* includePlaybackAndAppMetadata */
+                true);
         mAdapter = new MediaOutputAdapter(mMediaOutputController);
         // TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
         //  that extends MediaOutputBaseDialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 426a497..375a0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -78,7 +78,6 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -86,6 +85,7 @@
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -358,7 +358,7 @@
     }
 
     Drawable getAppSourceIconFromPackage() {
-        if (mPackageName.isEmpty()) {
+        if (TextUtils.isEmpty(mPackageName)) {
             return null;
         }
         try {
@@ -372,7 +372,7 @@
     }
 
     String getAppSourceName() {
-        if (mPackageName.isEmpty()) {
+        if (TextUtils.isEmpty(mPackageName)) {
             return null;
         }
         final PackageManager packageManager = mContext.getPackageManager();
@@ -391,7 +391,7 @@
     }
 
     Intent getAppLaunchIntent() {
-        if (mPackageName.isEmpty()) {
+        if (TextUtils.isEmpty(mPackageName)) {
             return null;
         }
         return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 4640a5d..d40699c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -27,10 +27,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
 
 /**
  * Dialog for media output transferring.
@@ -40,10 +40,15 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final UiEventLogger mUiEventLogger;
 
-    MediaOutputDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender,
-            MediaOutputController mediaOutputController, DialogLaunchAnimator dialogLaunchAnimator,
-            UiEventLogger uiEventLogger) {
-        super(context, broadcastSender, mediaOutputController);
+    MediaOutputDialog(
+            Context context,
+            boolean aboveStatusbar,
+            BroadcastSender broadcastSender,
+            MediaOutputController mediaOutputController,
+            DialogLaunchAnimator dialogLaunchAnimator,
+            UiEventLogger uiEventLogger,
+            boolean includePlaybackAndAppMetadata) {
+        super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata);
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mUiEventLogger = uiEventLogger;
         mAdapter = new MediaOutputAdapter(mMediaOutputController);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 2b38edb..b04a7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -61,6 +61,19 @@
 
     /** Creates a [MediaOutputDialog] for the given package. */
     open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+        create(packageName, aboveStatusBar, view, includePlaybackAndAppMetadata = true)
+    }
+
+    open fun createDialogForSystemRouting() {
+        create(packageName = null, aboveStatusBar = false, includePlaybackAndAppMetadata = false)
+    }
+
+    private fun create(
+            packageName: String?,
+            aboveStatusBar: Boolean,
+            view: View? = null,
+            includePlaybackAndAppMetadata: Boolean = true
+    ) {
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
@@ -71,7 +84,7 @@
             powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
-                    dialogLaunchAnimator, uiEventLogger)
+                    dialogLaunchAnimator, uiEventLogger, includePlaybackAndAppMetadata)
         mediaOutputDialog = dialog
 
         // Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 132bf99..1002cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -19,7 +19,6 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
-import android.text.TextUtils
 import android.util.Log
 import com.android.settingslib.media.MediaOutputConstants
 import javax.inject.Inject
@@ -35,16 +34,16 @@
     private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
-        when {
-            TextUtils.equals(
-                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action) -> {
+        when (intent.action) {
+            MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG -> {
                 val packageName: String? =
                     intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
                 launchMediaOutputDialogIfPossible(packageName)
             }
-            TextUtils.equals(
-                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG,
-                intent.action) -> {
+            MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
+                mediaOutputDialogFactory.createDialogForSystemRouting()
+            }
+            MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
                 val packageName: String? =
                     intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
                 launchMediaOutputBroadcastDialogIfPossible(packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 654fffe8..1983a67 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -24,6 +24,7 @@
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.view.WindowManager
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
 import android.widget.ImageView
@@ -106,6 +107,19 @@
         screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
+
+        // disable redundant Touch & Hold accessibility action for Switch Access
+        screenShareModeSpinner.accessibilityDelegate =
+            object : View.AccessibilityDelegate() {
+                override fun onInitializeAccessibilityNodeInfo(
+                    host: View,
+                    info: AccessibilityNodeInfo
+                ) {
+                    info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)
+                    super.onInitializeAccessibilityNodeInfo(host, info)
+                }
+            }
+        screenShareModeSpinner.isLongClickable = false
     }
 
     override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 5e3a166..e2e94da 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -72,6 +72,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.provider.DeviceConfig;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -736,17 +737,27 @@
     }
 
     public void destroyView() {
-        setAutoHideController(/* autoHideController */ null);
-        mCommandQueue.removeCallback(this);
-        mWindowManager.removeViewImmediate(mView.getRootView());
-        mNavigationModeController.removeListener(mModeChangedListener);
-        mEdgeBackGestureHandler.setStateChangeCallback(null);
+        Trace.beginSection("NavigationBar#destroyView");
+        try {
+            setAutoHideController(/* autoHideController */ null);
+            mCommandQueue.removeCallback(this);
+            Trace.beginSection("NavigationBar#removeViewImmediate");
+            try {
+                mWindowManager.removeViewImmediate(mView.getRootView());
+            } finally {
+                Trace.endSection();
+            }
+            mNavigationModeController.removeListener(mModeChangedListener);
+            mEdgeBackGestureHandler.setStateChangeCallback(null);
 
-        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mNotificationShadeDepthController.removeListener(mDepthListener);
+            mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+            mNotificationShadeDepthController.removeListener(mDepthListener);
 
-        mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
-        mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener);
+            mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+            mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener);
+        } finally {
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -940,50 +951,47 @@
 
     private void orientSecondaryHomeHandle() {
         if (!canShowSecondaryHandle()) {
+            if (mStartingQuickSwitchRotation == -1) {
+                resetSecondaryHandle();
+            }
             return;
         }
 
-        if (mStartingQuickSwitchRotation == -1) {
-            resetSecondaryHandle();
-        } else {
-            int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
-            if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
-                // Curious if starting quickswitch can change between the if check and our delta
-                Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
-                        + " current: " + mCurrentRotation
-                        + " starting: " + mStartingQuickSwitchRotation);
-            }
-            int height = 0;
-            int width = 0;
-            Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
-            mOrientationHandle.setDeltaRotation(deltaRotation);
-            switch (deltaRotation) {
-                case Surface.ROTATION_90:
-                case Surface.ROTATION_270:
-                    height = dispSize.height();
-                    width = mView.getHeight();
-                    break;
-                case Surface.ROTATION_180:
-                case Surface.ROTATION_0:
-                    // TODO(b/152683657): Need to determine best UX for this
-                    if (!mShowOrientedHandleForImmersiveMode) {
-                        resetSecondaryHandle();
-                        return;
-                    }
-                    width = dispSize.width();
-                    height = mView.getHeight();
-                    break;
-            }
-
-            mOrientationParams.gravity =
-                    deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
-                            (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
-            mOrientationParams.height = height;
-            mOrientationParams.width = width;
-            mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
-            mView.setVisibility(View.GONE);
-            mOrientationHandle.setVisibility(View.VISIBLE);
+        int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
+        if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
+            // Curious if starting quickswitch can change between the if check and our delta
+            Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
+                    + " current: " + mCurrentRotation
+                    + " starting: " + mStartingQuickSwitchRotation);
         }
+        int height = 0;
+        int width = 0;
+        Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mOrientationHandle.setDeltaRotation(deltaRotation);
+        switch (deltaRotation) {
+            case Surface.ROTATION_90, Surface.ROTATION_270:
+                height = dispSize.height();
+                width = mView.getHeight();
+                break;
+            case Surface.ROTATION_180, Surface.ROTATION_0:
+                // TODO(b/152683657): Need to determine best UX for this
+                if (!mShowOrientedHandleForImmersiveMode) {
+                    resetSecondaryHandle();
+                    return;
+                }
+                width = dispSize.width();
+                height = mView.getHeight();
+                break;
+        }
+
+        mOrientationParams.gravity =
+                deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
+                        (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
+        mOrientationParams.height = height;
+        mOrientationParams.width = width;
+        mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
+        mView.setVisibility(View.GONE);
+        mOrientationHandle.setVisibility(View.VISIBLE);
     }
 
     private void resetSecondaryHandle() {
@@ -1786,7 +1794,8 @@
     }
 
     private boolean canShowSecondaryHandle() {
-        return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
+        return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null
+                && mStartingQuickSwitchRotation != -1;
     }
 
     private final UserTracker.Callback mUserChangedCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 9f5e1b7..0320dec 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -278,6 +278,7 @@
         @Override
         public void onDisplayRemoved(int displayId) {
             removeNavigationBar(displayId);
+            mHasNavBar.delete(displayId);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3b32313e..8d1ff98a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -46,6 +46,7 @@
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
@@ -229,28 +230,34 @@
     }
 
     public void init(int displayId) {
-        if (mInitialized) {
-            return;
+        Trace.beginSection("TaskbarDelegate#init");
+        try {
+            if (mInitialized) {
+                return;
+            }
+            mDisplayId = displayId;
+            parseCurrentSysuiState();
+            mCommandQueue.addCallback(this);
+            mOverviewProxyService.addCallback(this);
+            onNavigationModeChanged(mNavigationModeController.addListener(this));
+            mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+            // Initialize component callback
+            Display display = mDisplayManager.getDisplay(displayId);
+            mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+            mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
+            // Set initial state for any listeners
+            updateSysuiFlags();
+            mAutoHideController.setNavigationBar(mAutoHideUiElement);
+            mLightBarController.setNavigationBar(mLightBarTransitionsController);
+            mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
+            mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
+            mEdgeBackGestureHandler.onConfigurationChanged(
+                    mContext.getResources().getConfiguration());
+            mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
+            mInitialized = true;
+        } finally {
+            Trace.endSection();
         }
-        mDisplayId = displayId;
-        parseCurrentSysuiState();
-        mCommandQueue.addCallback(this);
-        mOverviewProxyService.addCallback(this);
-        onNavigationModeChanged(mNavigationModeController.addListener(this));
-        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        // Initialize component callback
-        Display display = mDisplayManager.getDisplay(displayId);
-        mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
-        mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
-        // Set initial state for any listeners
-        updateSysuiFlags();
-        mAutoHideController.setNavigationBar(mAutoHideUiElement);
-        mLightBarController.setNavigationBar(mLightBarTransitionsController);
-        mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
-        mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
-        mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration());
-        mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
-        mInitialized = true;
     }
 
     public void destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 3dfd292..9846f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -578,10 +578,15 @@
      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
      */
     public void onNavigationModeChanged(int mode) {
-        mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode);
-        mInGestureNavMode = QuickStepContract.isGesturalMode(mode);
-        updateIsEnabled();
-        updateCurrentUserResources();
+        Trace.beginSection("EdgeBackGestureHandler#onNavigationModeChanged");
+        try {
+            mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode);
+            mInGestureNavMode = QuickStepContract.isGesturalMode(mode);
+            updateIsEnabled();
+            updateCurrentUserResources();
+        } finally {
+            Trace.endSection();
+        }
     }
 
     public void onNavBarTransientStateChanged(boolean isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d9a8080..270bfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -37,9 +37,10 @@
 import android.provider.Settings
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.res.R
+import com.android.app.tracing.TraceUtils.Companion.launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
@@ -47,6 +48,7 @@
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.util.settings.SecureSettings
@@ -54,8 +56,8 @@
 import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
 import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
 /**
  * Entry point for creating and managing note.
@@ -81,7 +83,8 @@
     private val devicePolicyManager: DevicePolicyManager,
     private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
-    @Application private val applicationScope: CoroutineScope
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgCoroutineContext: CoroutineContext
 ) {
 
     @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -172,7 +175,9 @@
     ) {
         if (!isEnabled) return
 
-        applicationScope.launch { awaitShowNoteTaskAsUser(entryPoint, user) }
+        applicationScope.launch("$TAG#showNoteTaskAsUser") {
+            awaitShowNoteTaskAsUser(entryPoint, user)
+        }
     }
 
     private suspend fun awaitShowNoteTaskAsUser(
@@ -337,7 +342,7 @@
 
     @InternalNoteTaskApi
     fun launchUpdateNoteTaskAsUser(user: UserHandle) {
-        applicationScope.launch {
+        applicationScope.launch("$TAG#launchUpdateNoteTaskAsUser", bgCoroutineContext) {
             if (!userManager.isUserUnlocked(user)) {
                 debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
                 return@launch
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 338d3ed..9698548d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
 import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.settings.UserTracker
@@ -52,6 +53,8 @@
 
     /** Initializes note task related features and glue it with other parts of the SystemUI. */
     fun initialize() {
+        debugLog { "initialize: isEnabled=$isEnabled, hasBubbles=${optionalBubbles.isEmpty}" }
+
         // Guard against feature not being enabled or mandatory dependencies aren't available.
         if (!isEnabled || optionalBubbles.isEmpty) return
 
@@ -134,12 +137,15 @@
      * Tracks a [KeyEvent], and determines if it should trigger an action to show the note task.
      * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise.
      */
-    private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? =
-        when {
+    private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? {
+        val entryPoint = when {
             keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
             keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
             else -> null
         }
+        debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" }
+        return entryPoint
+    }
 
     private var lastStylusButtonTailUpEventTime: Long = -MULTI_PRESS_TIMEOUT
 
@@ -155,8 +161,10 @@
         val isMultiPress = (downTime - lastStylusButtonTailUpEventTime) < MULTI_PRESS_TIMEOUT
         val isLongPress = (eventTime - downTime) >= LONG_PRESS_TIMEOUT
         lastStylusButtonTailUpEventTime = eventTime
+
         // For now, trigger action immediately on UP of a single press, without waiting for
         // the multi-press timeout to expire.
+        debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" }
         return !isMultiPress && !isLongPress
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 58c4f0d..ef72967 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -131,6 +131,9 @@
                     + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
                     + "?)*";
 
+    // Not all JDKs support emoji patterns, including the one errorprone runs under, which
+    // makes it think that this is an invalid pattern.
+    @SuppressWarnings("InvalidPatternSyntax")
     static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index fa18b35b..052c0da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -12,6 +12,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -22,6 +23,7 @@
 import android.widget.Scroller;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.viewpager.widget.PagerAdapter;
 import androidx.viewpager.widget.ViewPager;
 
@@ -43,6 +45,7 @@
     private static final int NO_PAGE = -1;
 
     private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
+    private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300;
     private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
     private static final long BOUNCE_ANIMATION_DURATION = 450L;
     private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
@@ -63,8 +66,9 @@
     private PageListener mPageListener;
 
     private boolean mListening;
-    private Scroller mScroller;
+    @VisibleForTesting Scroller mScroller;
 
+    /* set of animations used to indicate which tiles were just revealed  */
     @Nullable
     private AnimatorSet mBounceAnimatorSet;
     private float mLastExpansion;
@@ -306,6 +310,38 @@
         mPageIndicator = indicator;
         mPageIndicator.setNumPages(mPages.size());
         mPageIndicator.setLocation(mPageIndicatorPosition);
+        mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+                // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+                // have a chance to intercept ACTION_UP.
+                if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
+                    scrollByX(getDeltaXForKeyboardScrolling(keyCode),
+                            SINGLE_PAGE_SCROLL_DURATION_MILLIS);
+                }
+                return true;
+            }
+            return false;
+        });
+    }
+
+    private int getDeltaXForKeyboardScrolling(int keyCode) {
+        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+            return -getWidth();
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                && getCurrentItem() != mPages.size() - 1) {
+            return getWidth();
+        }
+        return 0;
+    }
+
+    private void scrollByX(int x, int durationMillis) {
+        if (x != 0) {
+            mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(),
+                    /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis);
+            // scroller just sets its state, we need to invalidate view to actually start scrolling
+            postInvalidateOnAnimation();
+        }
     }
 
     @Override
@@ -596,9 +632,7 @@
         });
         setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
         int dx = getWidth() * lastPageNumber;
-        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
-                REVEAL_SCROLL_DURATION_MILLIS);
-        postInvalidateOnAnimation();
+        scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS);
     }
 
     private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
index a321eef..6f5dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
@@ -18,17 +18,19 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.content.SharedPreferences
 import android.service.quicksettings.Tile
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
+import javax.inject.Inject
 import org.json.JSONException
 import org.json.JSONObject
-import javax.inject.Inject
 
 data class TileServiceKey(val componentName: ComponentName, val user: Int) {
     private val string = "${componentName.flattenToString()}:$user"
     override fun toString() = string
 }
+
 private const val STATE = "state"
 private const val LABEL = "label"
 private const val SUBTITLE = "subtitle"
@@ -44,12 +46,7 @@
  * It persists the state from a [Tile] necessary to present the view in the same state when
  * retrieved, with the exception of the icon.
  */
-class CustomTileStatePersister @Inject constructor(context: Context) {
-    companion object {
-        private const val FILE_NAME = "custom_tiles_state"
-    }
-
-    private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0)
+interface CustomTileStatePersister {
 
     /**
      * Read the state from [SharedPreferences].
@@ -58,7 +55,31 @@
      *
      * Any fields that have not been saved will be set to `null`
      */
-    fun readState(key: TileServiceKey): Tile? {
+    fun readState(key: TileServiceKey): Tile?
+    /**
+     * Persists the state into [SharedPreferences].
+     *
+     * The implementation does not store fields that are `null` or icons.
+     */
+    fun persistState(key: TileServiceKey, tile: Tile)
+    /**
+     * Removes the state for a given tile, user pair.
+     *
+     * Used when the tile is removed by the user.
+     */
+    fun removeState(key: TileServiceKey)
+}
+
+// TODO(b/299909989) Merge this class into into CustomTileRepository
+class CustomTileStatePersisterImpl @Inject constructor(context: Context) :
+    CustomTileStatePersister {
+    companion object {
+        private const val FILE_NAME = "custom_tiles_state"
+    }
+
+    private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0)
+
+    override fun readState(key: TileServiceKey): Tile? {
         val state = sharedPreferences.getString(key.toString(), null) ?: return null
         return try {
             readTileFromString(state)
@@ -68,23 +89,13 @@
         }
     }
 
-    /**
-     * Persists the state into [SharedPreferences].
-     *
-     * The implementation does not store fields that are `null` or icons.
-     */
-    fun persistState(key: TileServiceKey, tile: Tile) {
+    override fun persistState(key: TileServiceKey, tile: Tile) {
         val state = writeToString(tile)
 
         sharedPreferences.edit().putString(key.toString(), state).apply()
     }
 
-    /**
-     * Removes the state for a given tile, user pair.
-     *
-     * Used when the tile is removed by the user.
-     */
-    fun removeState(key: TileServiceKey) {
+    override fun removeState(key: TileServiceKey) {
         sharedPreferences.edit().remove(key.toString()).apply()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index f8e0159..4565200 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -45,6 +45,7 @@
 import android.widget.Switch
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.animation.LaunchableView
@@ -707,7 +708,7 @@
 
     inner class StateChangeRunnable(private val state: QSTile.State) : Runnable {
         override fun run() {
-            handleStateChanged(state)
+            traceSection("QSTileViewImpl#handleStateChanged") { handleStateChanged(state) }
         }
 
         // We want all instances of this runnable to be equal to each other, so they can be used to
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index 94137c8..4a34276 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.di
 
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.CustomTileStatePersisterImpl
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl
 import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
@@ -52,4 +54,7 @@
     fun bindQSTileIntentUserInputHandler(
         impl: QSTileIntentUserInputHandlerImpl
     ): QSTileIntentUserInputHandler
+
+    @Binds
+    fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 5bdb592..db3cf0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -81,10 +81,8 @@
     private lateinit var toggleView: Switch
     private lateinit var subtitleTextView: TextView
     private lateinit var doneButton: View
-    private lateinit var seeAllViewGroup: View
-    private lateinit var pairNewDeviceViewGroup: View
-    private lateinit var seeAllRow: View
-    private lateinit var pairNewDeviceRow: View
+    private lateinit var seeAllButton: View
+    private lateinit var pairNewDeviceButton: View
     private lateinit var deviceListView: RecyclerView
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -99,10 +97,8 @@
         toggleView = requireViewById(R.id.bluetooth_toggle)
         subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
         doneButton = requireViewById(R.id.done_button)
-        seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
-        pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
-        seeAllRow = requireViewById(R.id.see_all_clickable_row)
-        pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row)
+        seeAllButton = requireViewById(R.id.see_all_button)
+        pairNewDeviceButton = requireViewById(R.id.pair_new_device_button)
         deviceListView = requireViewById<RecyclerView>(R.id.device_list)
 
         setupToggle()
@@ -110,8 +106,8 @@
 
         subtitleTextView.text = context.getString(subtitleResIdInitialValue)
         doneButton.setOnClickListener { dismiss() }
-        seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
-        pairNewDeviceRow.setOnClickListener {
+        seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
+        pairNewDeviceButton.setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
     }
@@ -134,8 +130,8 @@
             }
             if (isActive) {
                 deviceItemAdapter.refreshDeviceItemList(deviceItem) {
-                    seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
-                    pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+                    seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE
+                    pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE
                     lastUiUpdateMs = systemClock.elapsedRealtime()
                     lastItemRow = itemRow
                     logger.logDeviceUiUpdate(lastUiUpdateMs - start)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 34c2aba..5d5e747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.os.Bundle
 import android.view.View
-import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogCuj
@@ -40,6 +39,8 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.produce
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -63,26 +64,25 @@
 
     private var job: Job? = null
 
-    @VisibleForTesting internal var dialog: BluetoothTileDialog? = null
-
     /**
      * Shows the dialog.
      *
      * @param context The context in which the dialog is displayed.
      * @param view The view from which the dialog is shown.
      */
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
     fun showDialog(context: Context, view: View?) {
-        dismissDialog()
-
-        var updateDeviceItemJob: Job? = null
-        var updateDialogUiJob: Job? = null
+        cancelJob()
 
         job =
             coroutineScope.launch(mainDispatcher) {
-                dialog = createBluetoothTileDialog(context)
+                var updateDeviceItemJob: Job?
+                var updateDialogUiJob: Job? = null
+                val dialog = createBluetoothTileDialog(context)
+
                 view?.let {
                     dialogLaunchAnimator.showFromView(
-                        dialog!!,
+                        dialog,
                         it,
                         animateBackgroundBoundsChange = true,
                         cuj =
@@ -92,9 +92,8 @@
                             )
                     )
                 }
-                    ?: dialog!!.show()
+                    ?: dialog.show()
 
-                updateDeviceItemJob?.cancel()
                 updateDeviceItemJob = launch {
                     deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
                 }
@@ -102,7 +101,7 @@
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
-                        dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it))
+                        dialog.onBluetoothStateUpdated(it, getSubtitleResId(it))
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
                             deviceItemInteractor.updateDeviceItems(
@@ -129,7 +128,7 @@
                     .onEach {
                         updateDialogUiJob?.cancel()
                         updateDialogUiJob = launch {
-                            dialog?.onDeviceItemUpdated(
+                            dialog.onDeviceItemUpdated(
                                 it.take(MAX_DEVICE_ITEM_ENTRY),
                                 showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
                                 showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
@@ -138,15 +137,15 @@
                     }
                     .launchIn(this)
 
-                dialog!!
-                    .bluetoothStateToggle
+                dialog.bluetoothStateToggle
                     .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
                     .launchIn(this)
 
-                dialog!!
-                    .deviceItemClick
+                dialog.deviceItemClick
                     .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
+
+                produce<Unit> { awaitClose { dialog.cancel() } }
             }
     }
 
@@ -161,7 +160,7 @@
                 logger,
                 context
             )
-            .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
+            .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } }
     }
 
     override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
@@ -188,15 +187,13 @@
         startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
     }
 
-    private fun dismissDialog() {
+    private fun cancelJob() {
         job?.cancel()
         job = null
-        dialog?.dismiss()
-        dialog = null
     }
 
     private fun startSettingsActivity(intent: Intent, view: View) {
-        dialog?.run {
+        if (job?.isActive == true) {
             intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
             activityStarter.postStartActivityDismissingKeyguard(
                 intent,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index 76fbf8e..fcd45a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -168,26 +168,30 @@
         )
     }
 
-    internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
-        logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
+    internal suspend fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
+        withContext(backgroundDispatcher) {
+            logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
 
-        deviceItem.cachedBluetoothDevice.apply {
-            when (deviceItem.type) {
-                DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
-                    disconnect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
-                }
-                DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
-                    setActive()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
-                }
-                DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
-                    disconnect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT)
-                }
-                DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
-                    connect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+            deviceItem.cachedBluetoothDevice.apply {
+                when (deviceItem.type) {
+                    DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
+                    }
+                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+                        setActive()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
+                    }
+                    DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(
+                            BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT
+                        )
+                    }
+                    DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+                        connect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
new file mode 100644
index 0000000..cfb5442
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.airplane.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [AirplaneModeTileModel] to [QSTileState]. */
+class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) :
+    QSTileDataToStateMapper<AirplaneModeTileModel> {
+
+    override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
+        QSTileState.build(resources, config.uiConfig) {
+            val icon =
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_airplane_icon_on
+                    } else {
+                        R.drawable.qs_airplane_icon_off
+                    },
+                    contentDescription = null
+                )
+            this.icon = { icon }
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[1]
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(
+                    QSTileState.UserAction.CLICK,
+                    QSTileState.UserAction.LONG_CLICK,
+                )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt
new file mode 100644
index 0000000..4f01a04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes airplane mode state changes providing the [AirplaneModeTileModel]. */
+class AirplaneModeTileDataInteractor
+@Inject
+constructor(
+    private val airplaneModeRepository: AirplaneModeRepository,
+) : QSTileDataInteractor<AirplaneModeTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<AirplaneModeTileModel> =
+        airplaneModeRepository.isAirplaneMode.map { AirplaneModeTileModel(it) }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt
new file mode 100644
index 0000000..9e13a56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.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
+ *
+ *      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.qs.tiles.impl.airplane.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import android.telephony.TelephonyManager
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class AirplaneModeTileUserActionInteractor
+@Inject
+constructor(
+    private val airplaneModeInteractor: AirplaneModeInteractor,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<AirplaneModeTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<AirplaneModeTileModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    when (airplaneModeInteractor.setIsAirplaneMode(!data.isEnabled)) {
+                        AirplaneModeInteractor.SetResult.SUCCESS -> {
+                            // do nothing
+                        }
+                        AirplaneModeInteractor.SetResult.BLOCKED_BY_ECM -> {
+                            qsTileIntentUserActionHandler.handle(
+                                action.view,
+                                Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS),
+                            )
+                        }
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.kt
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.kt
index 8e55695..7bf3b7d 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.qs.tiles.impl.airplane.domain.model
 
-import org.robolectric.annotation.GraphicsMode;
+/**
+ * Airplane mode tile model.
+ *
+ * @param isEnabled is true when the airplane mode is enabled;
+ */
+@JvmInline value class AirplaneModeTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt
new file mode 100644
index 0000000..869f6f32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.qs.tiles.impl.custom.commons
+
+import android.service.quicksettings.Tile
+
+fun Tile.copy(): Tile =
+    Tile().also {
+        it.icon = icon
+        it.label = label
+        it.subtitle = subtitle
+        it.contentDescription = contentDescription
+        it.stateDescription = stateDescription
+        it.activityLaunchForClick = activityLaunchForClick
+        it.state = state
+    }
+
+fun Tile.setFrom(otherTile: Tile) {
+    if (otherTile.icon != null) {
+        icon = otherTile.icon
+    }
+    if (otherTile.customLabel != null) {
+        label = otherTile.customLabel
+    }
+    if (otherTile.subtitle != null) {
+        subtitle = otherTile.subtitle
+    }
+    if (otherTile.contentDescription != null) {
+        contentDescription = otherTile.contentDescription
+    }
+    if (otherTile.stateDescription != null) {
+        stateDescription = otherTile.stateDescription
+    }
+    activityLaunchForClick = otherTile.activityLaunchForClick
+    state = otherTile.state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
new file mode 100644
index 0000000..ca5302e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.qs.tiles.impl.custom.data.repository
+
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.commons.setFrom
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which
+ * allows it to survive service rebinding. Given that, it provides the last received state when
+ * connected again.
+ */
+interface CustomTileRepository {
+
+    /**
+     * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile]
+     * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a
+     * corresponding [user].
+     */
+    suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean)
+
+    /** Returns [Tile] updates for a [user]. */
+    fun getTiles(user: UserHandle): Flow<Tile>
+
+    /**
+     * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one.
+     * Suspending until [getTiles] returns something is a way to wait for this to become available.
+     *
+     * @throws IllegalStateException when there is no current tile.
+     */
+    fun getTile(user: UserHandle): Tile?
+
+    /**
+     * Updates tile with the non-null values from [newTile]. Overwrites the current cache when
+     * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
+     * loaded when the [restoreForTheUserIfNeeded].
+     */
+    suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    )
+
+    /**
+     * Updates tile with the values from [defaults]. Overwrites the current cache when [user]
+     * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded
+     * when the [restoreForTheUserIfNeeded].
+     */
+    suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    )
+}
+
+@QSTileScope
+class CustomTileRepositoryImpl
+@Inject
+constructor(
+    private val tileSpec: TileSpec.CustomTileSpec,
+    private val customTileStatePersister: CustomTileStatePersister,
+    @Background private val backgroundContext: CoroutineContext,
+) : CustomTileRepository {
+
+    private val tileUpdateMutex = Mutex()
+    private val tileWithUserState =
+        MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1)
+
+    override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) {
+        if (isPersistable && getCurrentTileWithUser()?.user != user) {
+            withContext(backgroundContext) {
+                customTileStatePersister.readState(user.getKey())?.let {
+                    updateWithTile(
+                        user,
+                        it,
+                        true,
+                    )
+                }
+            }
+        }
+    }
+
+    override fun getTiles(user: UserHandle): Flow<Tile> =
+        tileWithUserState.filter { it.user == user }.map { it.tile }
+
+    override fun getTile(user: UserHandle): Tile? {
+        val tileWithUser =
+            getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set")
+        return if (tileWithUser.user == user) {
+            tileWithUser.tile
+        } else {
+            null
+        }
+    }
+
+    override suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    ) = updateTile(user, isPersistable) { setFrom(newTile) }
+
+    override suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    ) {
+        if (defaults is CustomTileDefaults.Result) {
+            updateTile(user, isPersistable) {
+                // Update the icon if it's not set or is the default icon.
+                val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon))
+                if (updateIcon) {
+                    icon = defaults.icon
+                }
+                setDefaultLabel(defaults.label)
+            }
+        }
+    }
+
+    private suspend fun updateTile(
+        user: UserHandle,
+        isPersistable: Boolean,
+        update: Tile.() -> Unit
+    ): Unit =
+        tileUpdateMutex.withLock {
+            val currentTileWithUser = getCurrentTileWithUser()
+            val tileToUpdate =
+                if (currentTileWithUser?.user == user) {
+                    currentTileWithUser.tile.copy()
+                } else {
+                    Tile()
+                }
+            tileToUpdate.update()
+            if (isPersistable) {
+                withContext(backgroundContext) {
+                    customTileStatePersister.persistState(user.getKey(), tileToUpdate)
+                }
+            }
+            tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate))
+        }
+
+    private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull()
+
+    /** Compare two icons, only works for resources. */
+    private fun Icon.isResourceEqual(icon2: Icon?): Boolean {
+        if (icon2 == null) {
+            return false
+        }
+        if (this === icon2) {
+            return true
+        }
+        if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) {
+            return false
+        }
+        if (resId != icon2.resId) {
+            return false
+        }
+        return resPackage == icon2.resPackage
+    }
+
+    private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
+
+    private data class TileWithUser(val user: UserHandle, val tile: Tile)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index 83767aa..d956fde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import dagger.Binds
@@ -50,4 +52,6 @@
     fun bindCustomTileDefaultsRepository(
         impl: CustomTileDefaultsRepositoryImpl
     ): CustomTileDefaultsRepository
+
+    @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
new file mode 100644
index 0000000..351bba5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.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
+ *
+ *      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.qs.tiles.impl.custom.domain.interactor
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/** Manages updates of the [Tile] assigned for the current custom tile. */
+@CustomTileBoundScope
+class CustomTileInteractor
+@Inject
+constructor(
+    private val user: UserHandle,
+    private val defaultsRepository: CustomTileDefaultsRepository,
+    private val customTileRepository: CustomTileRepository,
+    private val tileServiceManager: TileServiceManager,
+    @CustomTileBoundScope private val boundScope: CoroutineScope,
+    @Background private val backgroundContext: CoroutineContext,
+) {
+
+    private val tileUpdates =
+        MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    /** [Tile] updates. [updateTile] to emit a new one. */
+    val tiles: Flow<Tile>
+        get() = customTileRepository.getTiles(user)
+
+    /**
+     * Current [Tile]
+     *
+     * @throws IllegalStateException when the repository stores a tile for another user. This means
+     *   the tile hasn't been updated for the current user. Can happen when this is accessed before
+     *   [init] returns.
+     */
+    val tile: Tile
+        get() =
+            customTileRepository.getTile(user)
+                ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+    /**
+     * Initializes the repository for the current user. Suspends until it's safe to call [tile]
+     * which needs at least one of the following:
+     * - defaults are loaded;
+     * - receive tile update in [updateTile];
+     * - restoration happened for a persisted tile.
+     */
+    suspend fun init() {
+        launchUpdates()
+        customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+        // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
+        // tile update.
+        customTileRepository.getTiles(user).firstOrNull()
+    }
+
+    private fun launchUpdates() {
+        tileUpdates
+            .onEach {
+                customTileRepository.updateWithTile(
+                    user,
+                    it,
+                    tileServiceManager.isActiveTile,
+                )
+            }
+            .flowOn(backgroundContext)
+            .launchIn(boundScope)
+        defaultsRepository
+            .defaults(user)
+            .onEach {
+                customTileRepository.updateWithDefaults(
+                    user,
+                    it,
+                    tileServiceManager.isActiveTile,
+                )
+            }
+            .flowOn(backgroundContext)
+            .launchIn(boundScope)
+    }
+
+    /** Updates current [Tile]. Emits a new event in [tiles]. */
+    fun updateTile(newTile: Tile) {
+        tileUpdates.tryEmit(newTile)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
new file mode 100644
index 0000000..881a6bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.flashlight.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [FlashlightTileModel] to [QSTileState]. */
+class FlashlightMapper @Inject constructor(@Main private val resources: Resources) :
+    QSTileDataToStateMapper<FlashlightTileModel> {
+
+    override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
+        QSTileState.build(resources, config.uiConfig) {
+            val icon =
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_flashlight_icon_on
+                    } else {
+                        R.drawable.qs_flashlight_icon_off
+                    },
+                    contentDescription = null
+                )
+            this.icon = { icon }
+
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(
+                    QSTileState.UserAction.CLICK,
+                )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
new file mode 100644
index 0000000..53d4cf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.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
+ *
+ *      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.qs.tiles.impl.flashlight.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.statusbar.policy.FlashlightController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes flashlight state changes providing the [FlashlightTileModel]. */
+class FlashlightTileDataInteractor
+@Inject
+constructor(
+    private val flashlightController: FlashlightController,
+) : QSTileDataInteractor<FlashlightTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<FlashlightTileModel> = conflatedCallbackFlow {
+        val initialValue = flashlightController.isEnabled
+        trySend(FlashlightTileModel(initialValue))
+
+        val callback =
+            object : FlashlightController.FlashlightListener {
+                override fun onFlashlightChanged(enabled: Boolean) {
+                    trySend(FlashlightTileModel(enabled))
+                }
+                override fun onFlashlightError() {
+                    trySend(FlashlightTileModel(false))
+                }
+                override fun onFlashlightAvailabilityChanged(available: Boolean) {
+                    trySend(FlashlightTileModel(flashlightController.isEnabled))
+                }
+            }
+        flashlightController.addCallback(callback)
+        awaitClose { flashlightController.removeCallback(callback) }
+    }
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(flashlightController.hasFlashlight())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
new file mode 100644
index 0000000..9180e30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
+
+import android.app.ActivityManager
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.FlashlightController
+import javax.inject.Inject
+
+/** Handles flashlight tile clicks. */
+class FlashlightTileUserActionInteractor
+@Inject
+constructor(
+    private val flashlightController: FlashlightController,
+) : QSTileUserActionInteractor<FlashlightTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<FlashlightTileModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    if (!ActivityManager.isUserAMonkey()) {
+                        flashlightController.setFlashlight(!input.data.isEnabled)
+                    }
+                }
+                else -> {}
+            }
+        }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index 8e55695..ef6b2be 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.qs.tiles.impl.flashlight.domain.model
 
-import org.robolectric.annotation.GraphicsMode;
+/**
+ * Flashlight tile model.
+ *
+ * @param isEnabled is true when the falshlight is enabled;
+ */
+@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
new file mode 100644
index 0000000..7e7034d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.location.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [LocationTileModel] to [QSTileState]. */
+class LocationTileMapper @Inject constructor(@Main private val resources: Resources) :
+    QSTileDataToStateMapper<LocationTileModel> {
+
+    override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
+        QSTileState.build(resources, config.uiConfig) {
+            val icon =
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_location_icon_on
+                    } else {
+                        R.drawable.qs_location_icon_off
+                    },
+                    contentDescription = null
+                )
+            this.icon = { icon }
+
+            this.label = resources.getString(R.string.quick_settings_location_label)
+
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_location)[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = resources.getStringArray(R.array.tile_states_location)[1]
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
new file mode 100644
index 0000000..d1c8030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.qs.tiles.impl.location.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.statusbar.policy.LocationController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes location state changes providing the [LocationTileModel]. */
+class LocationTileDataInteractor
+@Inject
+constructor(
+    private val locationController: LocationController,
+) : QSTileDataInteractor<LocationTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<LocationTileModel> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val initialValue = locationController.isLocationEnabled
+            trySend(LocationTileModel(initialValue))
+
+            val callback =
+                object : LocationController.LocationChangeCallback {
+                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+                        trySend(LocationTileModel(locationEnabled))
+                    }
+                }
+            locationController.addCallback(callback)
+            awaitClose { locationController.removeCallback(callback) }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
new file mode 100644
index 0000000..66705ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.qs.tiles.impl.location.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.LocationController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/** Handles location tile clicks. */
+class LocationTileUserActionInteractor
+@Inject
+constructor(
+    @Background private val coroutineContext: CoroutineContext,
+    @Application private val applicationScope: CoroutineScope,
+    private val locationController: LocationController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val activityStarter: ActivityStarter,
+    private val keyguardController: KeyguardStateController,
+) : QSTileUserActionInteractor<LocationTileModel> {
+    override suspend fun handleInput(input: QSTileInput<LocationTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    val wasEnabled: Boolean = input.data.isEnabled
+                    if (keyguardController.isMethodSecure() && keyguardController.isShowing()) {
+                        activityStarter.postQSRunnableDismissingKeyguard {
+                            CoroutineScope(applicationScope.coroutineContext).launch {
+                                locationController.setLocationEnabled(!wasEnabled)
+                            }
+                        }
+                    } else {
+                        withContext(coroutineContext) {
+                            locationController.setLocationEnabled(!wasEnabled)
+                        }
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt
index 8e55695..3095d7e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.qs.tiles.impl.location.domain.model
 
-import org.robolectric.annotation.GraphicsMode;
+/**
+ * Location tile model.
+ *
+ * @param isEnabled is true when the location is enabled;
+ */
+@JvmInline value class LocationTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index f9e0b16..23e0cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.content.Context
+import android.content.res.Resources
 import android.service.quicksettings.Tile
 import android.view.View
 import android.widget.Switch
@@ -46,13 +46,13 @@
     companion object {
 
         fun build(
-            context: Context,
+            resources: Resources,
             config: QSTileUIConfig,
             build: Builder.() -> Unit
         ): QSTileState =
             build(
                 { Icon.Resource(config.iconRes, null) },
-                context.getString(config.labelRes),
+                resources.getString(config.labelRes),
                 build,
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 771d07c..e8623f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -101,7 +101,10 @@
 
     override fun addCallback(callback: QSTile.Callback?) {
         callback ?: return
-        synchronized(callbacks) { callbacks.add(callback) }
+        synchronized(callbacks) {
+            callbacks.add(callback)
+            state?.let(callback::onStateChanged)
+        }
     }
 
     override fun removeCallback(callback: QSTile.Callback?) {
@@ -234,6 +237,9 @@
                 disabledByPolicy = viewModelState.enabledState == QSTileState.EnabledState.DISABLED
                 expandedAccessibilityClassName = viewModelState.expandedAccessibilityClassName
 
+                // Use LoopedAnimatable2DrawableWrapper to achieve animated tile icon
+                isTransient = false
+
                 when (viewModelState.sideViewIcon) {
                     is QSTileState.SideViewIcon.Custom -> {
                         sideViewCustomDrawable =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 3941fd7..e5e1e84 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -17,13 +17,13 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.map
 
@@ -32,13 +32,10 @@
 class QuickSettingsSceneViewModel
 @Inject
 constructor(
-    private val deviceEntryInteractor: DeviceEntryInteractor,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val qsSceneAdapter: QSSceneAdapter,
+    val notifications: NotificationsPlaceholderViewModel,
 ) {
-    /** Notifies that some content in quick settings was clicked. */
-    fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
-
     val destinationScenes =
         qsSceneAdapter.isCustomizing.map { customizing ->
             if (customizing) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 8def457..1c5330e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -97,6 +97,7 @@
     /** Updates the visibility of the scene container. */
     private fun hydrateVisibility() {
         applicationScope.launch {
+            // TODO(b/296114544): Combine with some global hun state to make it visible!
             sceneInteractor.transitionState
                 .mapNotNull { state ->
                     when (state) {
@@ -145,19 +146,21 @@
                     isAnySimLocked -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Bouncer,
-                            loggingReason = "Need to authenticate locked sim card."
+                            loggingReason = "Need to authenticate locked SIM card."
                         )
                     }
-                    isUnlocked && !canSwipeToEnter -> {
+                    isUnlocked && canSwipeToEnter == false -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Gone,
-                            loggingReason = "Sim cards are unlocked."
+                            loggingReason = "All SIM cards unlocked and device already" +
+                                " unlocked and lockscreen doesn't require a swipe to dismiss."
                         )
                     }
                     else -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Lockscreen,
-                            loggingReason = "Sim cards are unlocked."
+                            loggingReason = "All SIM cards unlocked and device still locked" +
+                                " or lockscreen still requires a swipe to dismiss."
                         )
                     }
                 }
@@ -204,11 +207,17 @@
                             //    when the user is passively authenticated, the false value here
                             //    when the unlock state changes indicates this is an active
                             //    authentication attempt.
-                            if (isBypassEnabled || !canSwipeToEnter)
-                                SceneKey.Gone to
-                                    "device has been unlocked on lockscreen with either " +
-                                        "bypass enabled or using an active authentication mechanism"
-                            else null
+                            when {
+                                isBypassEnabled ->
+                                    SceneKey.Gone to
+                                        "device has been unlocked on lockscreen with bypass" +
+                                            " enabled"
+                                canSwipeToEnter == false ->
+                                    SceneKey.Gone to
+                                        "device has been unlocked on lockscreen using an active" +
+                                            " authentication mechanism"
+                                else -> null
+                            }
                         // Not on lockscreen or bouncer, so remain in the current scene.
                         else -> null
                     }
@@ -231,7 +240,7 @@
                 } else {
                     val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
                     val isUnlocked = deviceEntryInteractor.isUnlocked.value
-                    if (isUnlocked && !canSwipeToEnter) {
+                    if (isUnlocked && canSwipeToEnter == false) {
                         switchToScene(
                             targetSceneKey = SceneKey.Gone,
                             loggingReason =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index d14ef35..dbb58a3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.scene.shared.flag
 
+import android.content.Context
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.sceneContainer
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flag
 import com.android.systemui.flags.Flags
@@ -29,6 +31,7 @@
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.res.R
 import dagger.Module
 import dagger.Provides
 import dagger.assisted.Assisted
@@ -51,6 +54,7 @@
 class SceneContainerFlagsImpl
 @AssistedInject
 constructor(
+    @Application private val context: Context,
     private val featureFlagsClassic: FeatureFlagsClassic,
     @Assisted private val isComposeAvailable: Boolean,
 ) : SceneContainerFlags {
@@ -80,7 +84,11 @@
             ),
         ) +
             classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
-            listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
+            listOf(
+                ComposeMustBeAvailable(),
+                CompileTimeFlagMustBeEnabled(),
+                ResourceConfigMustBeEnabled()
+            )
 
     override fun isEnabled(): Boolean {
         // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
@@ -146,6 +154,14 @@
         }
     }
 
+    private inner class ResourceConfigMustBeEnabled : Requirement {
+        override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
+
+        override fun isMet(): Boolean {
+            return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 7fc4094..c88a04c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -4,9 +4,11 @@
 import android.util.AttributeSet
 import android.view.View
 import android.view.WindowInsets
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** A root view of the main SysUI window that supports scenes. */
@@ -27,6 +29,8 @@
     fun init(
         viewModel: SceneContainerViewModel,
         containerConfig: SceneContainerConfig,
+        sharedNotificationContainer: SharedNotificationContainer,
+        flags: SceneContainerFlags,
         scenes: Set<Scene>,
         layoutInsetController: LayoutInsetsController,
     ) {
@@ -37,6 +41,8 @@
             viewModel = viewModel,
             windowInsets = windowInsets,
             containerConfig = containerConfig,
+            sharedNotificationContainer = sharedNotificationContainer,
+            flags = flags,
             scenes = scenes,
             onVisibilityChangedInternal = { isVisible ->
                 super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 17d6c9d..4a839b8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -31,10 +31,13 @@
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import java.time.Instant
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
@@ -47,6 +50,8 @@
         viewModel: SceneContainerViewModel,
         windowInsets: StateFlow<WindowInsets?>,
         containerConfig: SceneContainerConfig,
+        sharedNotificationContainer: SharedNotificationContainer,
+        flags: SceneContainerFlags,
         scenes: Set<Scene>,
         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
     ) {
@@ -91,6 +96,13 @@
                     val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
                     view.addView(createVisibilityToggleView(legacyView))
 
+                    if (flags.flexiNotifsEnabled()) {
+                        (sharedNotificationContainer.parent as? ViewGroup)?.removeView(
+                            sharedNotificationContainer
+                        )
+                        view.addView(sharedNotificationContainer)
+                    }
+
                     launch {
                         viewModel.isVisible.collect { isVisible ->
                             onVisibilityChangedInternal(isVisible)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index e57a0fd..3f6c58d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.screenrecord
 
+import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.PendingIntent
 import android.content.Intent
@@ -23,6 +24,7 @@
 import android.os.Looper
 import android.os.ResultReceiver
 import android.os.UserHandle
+import android.view.MotionEvent.ACTION_MOVE
 import android.view.View
 import android.view.View.GONE
 import android.view.View.VISIBLE
@@ -102,11 +104,19 @@
 
     @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
 
+    @SuppressLint("ClickableViewAccessibility")
     private fun initRecordOptionsView() {
         audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch)
         tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch)
+
+        // Add these listeners so that the switch only responds to movement
+        // within its target region, to meet accessibility requirements
+        audioSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
+        tapsSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
+
         tapsView = dialog.requireViewById(R.id.show_taps)
         updateTapsViewVisibility()
+
         options = dialog.requireViewById(R.id.screen_recording_options)
         val a: ArrayAdapter<*> =
             ScreenRecordingAdapter(
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index f4d19dc..d0585d3 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -86,6 +86,8 @@
     public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
+        setFocusable(false);
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         mDrawable = new ScrimDrawable();
         mDrawable.setCallback(this);
         mColors = new ColorExtractor.GradientColors();
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 b30bc56..bc5090f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,12 +30,11 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -282,7 +281,6 @@
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
         private final CoroutineDispatcher mMainDispatcher;
-        private final ActivityStarter mActivityStarter;
 
         @Inject
         public Factory(
@@ -290,14 +288,13 @@
                 UiEventLogger uiEventLogger,
                 VibratorHelper vibratorHelper,
                 SystemClock clock,
-                @Main CoroutineDispatcher mainDispatcher,
-                ActivityStarter activityStarter) {
+                @Main CoroutineDispatcher mainDispatcher
+        ) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
             mMainDispatcher = mainDispatcher;
-            mActivityStarter = activityStarter;
         }
 
         /**
@@ -313,8 +310,6 @@
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
-            root.setActivityStarter(mActivityStarter);
-
             BrightnessSliderHapticPlugin plugin;
             if (hapticBrightnessSlider()) {
                 plugin = new BrightnessSliderHapticPluginImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 5ecf07f..c885492 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -33,7 +33,6 @@
 
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 
 /**
@@ -42,7 +41,6 @@
  */
 public class BrightnessSliderView extends FrameLayout {
 
-    private ActivityStarter mActivityStarter;
     @NonNull
     private ToggleSeekBar mSlider;
     private DispatchTouchEventListener mListener;
@@ -59,10 +57,6 @@
         super(context, attrs);
     }
 
-    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
-        mActivityStarter = activityStarter;
-    }
-
     // Inflated from quick_settings_brightness_dialog
     @Override
     protected void onFinishInflate() {
@@ -71,7 +65,6 @@
 
         mSlider = requireViewById(R.id.slider);
         mSlider.setAccessibilityLabel(getContentDescription().toString());
-        mSlider.setActivityStarter(mActivityStarter);
 
         // Finds the progress drawable. Assumes brightness_progress_drawable.xml
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index 6ec10da..a5a0ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -23,9 +23,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.SeekBar;
 
-import androidx.annotation.NonNull;
-
 import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ActivityStarter;
 
 public class ToggleSeekBar extends SeekBar {
@@ -33,8 +32,6 @@
 
     private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
 
-    private ActivityStarter mActivityStarter;
-
     public ToggleSeekBar(Context context) {
         super(context);
     }
@@ -52,7 +49,7 @@
         if (mEnforcedAdmin != null) {
             Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
                     mContext, mEnforcedAdmin);
-            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
             return true;
         }
         if (!isEnabled()) {
@@ -77,8 +74,4 @@
     public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
         mEnforcedAdmin = admin;
     }
-
-    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
-        mActivityStarter = activityStarter;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a44b4b4..67ec03f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -98,7 +98,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.ActiveUnlockConfig;
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
@@ -184,6 +183,8 @@
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -604,6 +605,7 @@
     private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
     private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+    private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
@@ -774,6 +776,7 @@
             KeyguardInteractor keyguardInteractor,
             ActivityStarter activityStarter,
             SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
+            ActiveNotificationsInteractor activeNotificationsInteractor,
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
@@ -804,6 +807,7 @@
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
+        mActiveNotificationsInteractor = activeNotificationsInteractor;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1298,6 +1302,7 @@
 
     @Override
     public void updateResources() {
+        Trace.beginSection("NSSLC#updateResources");
         final boolean newSplitShadeEnabled =
                 mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
         final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
@@ -1305,7 +1310,8 @@
         mQsController.updateResources();
         mNotificationsQSContainerController.updateResources();
         updateKeyguardStatusViewAlignment(/* animate= */false);
-        mKeyguardMediaController.refreshMediaPosition();
+        mKeyguardMediaController.refreshMediaPosition(
+                "NotificationPanelViewController.updateResources");
 
         if (splitShadeChanged) {
             onSplitShadeEnabledChanged();
@@ -1313,6 +1319,7 @@
 
         mSplitShadeFullTransitionDistance =
                 mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+        Trace.endSection();
     }
 
     private void onSplitShadeEnabledChanged() {
@@ -1795,9 +1802,14 @@
     }
 
     private boolean hasVisibleNotifications() {
-        return mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0
-                || mMediaDataManager.hasActiveMediaOrRecommendation();
+        if (FooterViewRefactor.isEnabled()) {
+            return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+                    || mMediaDataManager.hasActiveMediaOrRecommendation();
+        } else {
+            return mNotificationStackScrollLayoutController
+                    .getVisibleNotificationCount() != 0
+                    || mMediaDataManager.hasActiveMediaOrRecommendation();
+        }
     }
 
     /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */
@@ -2956,10 +2968,8 @@
                     // Try triggering face auth, this "might" run. Check
                     // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
                     mKeyguardFaceAuthInteractor.onNotificationPanelClicked();
-                    boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
-                            FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
 
-                    if (didFaceAuthRun) {
+                    if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
                         mUpdateMonitor.requestActiveUnlock(
                                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                                 "lockScreenEmptySpaceTap");
@@ -3004,7 +3014,9 @@
     @Override
     public void setBouncerShowing(boolean bouncerShowing) {
         mBouncerShowing = bouncerShowing;
-        mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
+        if (!FooterViewRefactor.isEnabled()) {
+            mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
+        }
         updateVisibility();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 335e65e..dd194eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -61,7 +61,6 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.policy.SystemBarUtils;
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
@@ -87,6 +86,8 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -106,12 +107,12 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
@@ -157,6 +158,7 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShadeRepository mShadeRepository;
     private final ShadeInteractor mShadeInteractor;
+    private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
     private final JavaAdapter mJavaAdapter;
     private final FalsingManager mFalsingManager;
     private final AccessibilityManager mAccessibilityManager;
@@ -247,6 +249,14 @@
     private Insets mCachedGestureInsets;
 
     /**
+     * The window width currently in effect -- used together with
+     * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should
+     * receive a horizontal swipe inwards from the left/right vertical edge of the screen.
+     * We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
+     */
+    private int mCachedWindowWidth;
+
+    /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade. This value can also go beyond 1.1 when we're overshooting!
@@ -331,6 +341,7 @@
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             ShadeRepository shadeRepository,
             ShadeInteractor shadeInteractor,
+            ActiveNotificationsInteractor activeNotificationsInteractor,
             JavaAdapter javaAdapter,
             CastController castController,
             SplitShadeStateController splitShadeStateController
@@ -378,6 +389,7 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
         mShadeInteractor = shadeInteractor;
+        mActiveNotificationsInteractor = activeNotificationsInteractor;
         mJavaAdapter = javaAdapter;
 
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
@@ -528,6 +540,7 @@
         WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
         mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
                 WindowInsets.Type.systemGestures());
+        mCachedWindowWidth = windowMetrics.getBounds().width();
     }
 
     /**
@@ -536,7 +549,7 @@
      */
     public boolean shouldBackBypassQuickSettings(float touchX) {
         return (touchX < mCachedGestureInsets.left)
-                || (touchX > mKeyguardStatusBar.getWidth() - mCachedGestureInsets.right);
+                || (touchX > mCachedWindowWidth - mCachedGestureInsets.right);
     }
 
     /** Returns whether touch is within QS area */
@@ -967,14 +980,15 @@
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
             mKeyguardFaceAuthInteractor.onQsExpansionStared();
-            mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
 
     void updateQsState() {
         boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
         mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
-        mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+        if (!FooterViewRefactor.isEnabled()) {
+            mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+        }
         mNotificationStackScrollLayoutController.setScrollingEnabled(
                 mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
 
@@ -2105,6 +2119,8 @@
         ipw.println(mAnimatorExpand);
         ipw.print("mCachedGestureInsets=");
         ipw.println(mCachedGestureInsets);
+        ipw.print("mCachedWindowWidth=");
+        ipw.println(mCachedWindowWidth);
         ipw.print("mTransitioningToFullShadeProgress=");
         ipw.println(mTransitioningToFullShadeProgress);
         ipw.print("mDistanceForFullShadeTransition=");
@@ -2219,8 +2235,12 @@
                             mLockscreenShadeTransitionController.getQSDragProgress());
                     setExpansionHeight(qsHeight);
                 }
-                if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
-                        && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
+
+                boolean hasNotifications = FooterViewRefactor.isEnabled()
+                        ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+                        : mNotificationStackScrollLayoutController.getVisibleNotificationCount()
+                                != 0;
+                if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
                     // No notifications are visible, let's animate to the height of qs instead
                     if (isQsFragmentCreated()) {
                         // Let's interpolate to the header height instead of the top padding,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 374e871..f40be4b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -69,6 +69,7 @@
             sceneContainerFlags: SceneContainerFlags,
             viewModelProvider: Provider<SceneContainerViewModel>,
             containerConfigProvider: Provider<SceneContainerConfig>,
+            flagsProvider: Provider<SceneContainerFlags>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
         ): WindowRootView {
@@ -78,6 +79,9 @@
                 sceneWindowRootView.init(
                     viewModel = viewModelProvider.get(),
                     containerConfig = containerConfigProvider.get(),
+                    sharedNotificationContainer =
+                        sceneWindowRootView.requireViewById(R.id.shared_notification_container),
+                    flags = flagsProvider.get(),
                     scenes = scenesProvider.get(),
                     layoutInsetController = layoutInsetController,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index af88081..0065db3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -21,12 +21,13 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
-import javax.inject.Inject
 
 /** Models UI state and handles user input for the shade scene. */
 @SysUISingleton
@@ -37,6 +38,7 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     val qsSceneAdapter: QSSceneAdapter,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
+    val notifications: NotificationsPlaceholderViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -64,10 +66,10 @@
 
     private fun upDestinationSceneKey(
         isUnlocked: Boolean,
-        canSwipeToDismiss: Boolean,
+        canSwipeToDismiss: Boolean?,
     ): SceneKey {
         return when {
-            canSwipeToDismiss -> SceneKey.Lockscreen
+            canSwipeToDismiss == true -> SceneKey.Lockscreen
             isUnlocked -> SceneKey.Gone
             else -> SceneKey.Lockscreen
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index de334bb..2338be2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -85,7 +85,9 @@
 
     public void setFooterVisibility(@Visibility int visibility) {
         mFooterVisibility = visibility;
-        setSecondaryVisible(visibility == View.VISIBLE, false);
+        setSecondaryVisible(/* visible = */ visibility == View.VISIBLE,
+                /* animate = */false,
+                /* onAnimationEnded = */ null);
     }
 
     public void setFooterText(@StringRes int text) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 68c48b9..1b096b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -29,8 +29,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.hardware.input.InputManagerGlobal;
@@ -60,6 +59,7 @@
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.FrameLayout;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
@@ -114,12 +114,10 @@
     private Button mButtonInput;
     private Button mButtonOpenApps;
     private Button mButtonSpecificApp;
-    private ImageView mEditTextCancel;
     private TextView mNoSearchResults;
 
     private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
     private final SparseArray<String> mModifierNames = new SparseArray<>();
-    private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
     private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
     // Ordered list of modifiers that are supported. All values in this array must exist in
     // mModifierNames.
@@ -146,7 +144,7 @@
         } else {
             this.mWindowManager = mContext.getSystemService(WindowManager.class);
         }
-        loadResources(context);
+        loadResources(this.mContext);
         createHardcodedShortcuts();
     }
 
@@ -287,7 +285,7 @@
         mSpecialCharacterNames.put(
                 KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MINUS, "-");
-        mSpecialCharacterNames.put(KeyEvent.KEYCODE_GRAVE, "~");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_GRAVE, "`");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_EQUALS, "=");
 
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
@@ -350,19 +348,6 @@
         mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
         mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
 
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
-
         mModifierDrawables.put(
                 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
     }
@@ -508,7 +493,7 @@
                         Arrays.asList(
                                 Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))),
                 /* Back: go back to previous state (back button) */
-                /* Meta + ~, Meta + backspace, Meta + left arrow */
+                /* Meta + Grave, Meta + backspace, Meta + left arrow */
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_go_back),
                         Arrays.asList(
@@ -826,11 +811,12 @@
                 new BottomSheetDialog(mContext);
         final View keyboardShortcutsView = inflater.inflate(
                 R.layout.keyboard_shortcuts_search_view, null);
+        LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById(
+                R.id.keyboard_shortcuts_container);
         mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
         mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
         setButtonsDefaultStatus(keyboardShortcutsView);
-        populateKeyboardShortcutSearchList(
-                keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_container));
+        populateKeyboardShortcutSearchList(shortcutsContainer);
 
         // Workaround for solve issue about dialog not full expanded when landscape.
         FrameLayout bottomSheet = (FrameLayout)
@@ -880,9 +866,14 @@
                     @Override
                     public void afterTextChanged(Editable s) {
                         mQueryString = s.toString();
-                        populateKeyboardShortcutSearchList(
-                                keyboardShortcutsView.findViewById(
-                                        R.id.keyboard_shortcuts_container));
+                        populateKeyboardShortcutSearchList(shortcutsContainer);
+                        if (mNoSearchResults.getVisibility() == View.VISIBLE) {
+                            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                                    R.string.keyboard_shortcut_search_list_no_result));
+                        } else if (mSearchEditText.getText().length() > 0) {
+                            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                                    R.string.keyboard_shortcut_a11y_show_search_results));
+                        }
                     }
 
                     @Override
@@ -895,8 +886,9 @@
                         // Do nothing.
                     }
                 });
-        mEditTextCancel = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search_cancel);
-        mEditTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+        ImageButton editTextCancel = keyboardShortcutsView.findViewById(
+                R.id.keyboard_shortcuts_search_cancel);
+        editTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
     }
 
     private void populateKeyboardShortcutSearchList(LinearLayout keyboardShortcutsLayout) {
@@ -1033,16 +1025,32 @@
                             StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k);
                             if (shortcutRepresentation.mDrawable != null) {
                                 ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
-                                        R.layout.keyboard_shortcuts_key_new_icon_view,
+                                        R.layout.keyboard_shortcuts_key_icon_view,
                                         shortcutItemsContainer,
                                         false);
-                                Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth,
-                                        shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888);
-                                Canvas canvas = new Canvas(bitmap);
-                                shortcutRepresentation.mDrawable.setBounds(0, 0, canvas.getWidth(),
-                                        canvas.getHeight());
-                                shortcutRepresentation.mDrawable.draw(canvas);
-                                shortcutKeyIconView.setImageBitmap(bitmap);
+                                shortcutKeyIconView.setImageDrawable(
+                                        shortcutRepresentation.mDrawable);
+                                // Once the view has been measured, scale and position the icon in
+                                // the center.
+                                shortcutKeyIconView.post(() -> {
+                                    Drawable d = shortcutKeyIconView.getDrawable();
+
+                                    float newSize = mContext.getResources().getDimensionPixelSize(
+                                            R.dimen.ksh_icon_scaled_size);
+                                    int viewWidth = shortcutKeyIconView.getWidth();
+                                    int viewHeight = shortcutKeyIconView.getHeight();
+                                    float scaleFactor = newSize / d.getIntrinsicWidth();
+                                    // Assumes that top/bottom and left/right padding are equal.
+                                    int paddingHorizontal =  shortcutKeyIconView.getPaddingLeft();
+                                    int paddingVertical =  shortcutKeyIconView.getPaddingTop();
+
+                                    Matrix m = new Matrix();
+                                    m.postScale(scaleFactor, scaleFactor);
+                                    m.postTranslate(
+                                            (viewWidth - newSize) / 2 - paddingHorizontal,
+                                            (viewHeight - newSize) / 2 - paddingVertical);
+                                    shortcutKeyIconView.setImageMatrix(m);
+                                });
                                 shortcutKeyIconView.setImportantForAccessibility(
                                         IMPORTANT_FOR_ACCESSIBILITY_YES);
                                 shortcutKeyIconView.setAccessibilityDelegate(
@@ -1051,7 +1059,7 @@
                                 shortcutItemsContainer.addView(shortcutKeyIconView);
                             } else if (shortcutRepresentation.mString != null) {
                                 TextView shortcutKeyTextView = (TextView) inflater.inflate(
-                                        R.layout.keyboard_shortcuts_key_new_view,
+                                        R.layout.keyboard_shortcuts_key_view,
                                         shortcutItemsContainer,
                                         false);
                                 shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
@@ -1061,18 +1069,10 @@
                                                 shortcutRepresentation.mString));
                                 shortcutItemsContainer.addView(shortcutKeyTextView);
                             }
-
-                            if (k < shortcutKeysSize - 1) {
-                                TextView shortcutKeyTextView = (TextView) inflater.inflate(
-                                        R.layout.keyboard_shortcuts_key_plus_view,
-                                        shortcutItemsContainer,
-                                        false);
-                                shortcutItemsContainer.addView(shortcutKeyTextView);
-                            }
                         }
                     } else {
                         TextView shortcutKeyTextView = (TextView) inflater.inflate(
-                                R.layout.keyboard_shortcuts_key_new_view,
+                                R.layout.keyboard_shortcuts_key_view,
                                 shortcutItemsContainer,
                                 false);
                         shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
@@ -1084,7 +1084,7 @@
 
                     if (p < keyGroupItemsSize - 1) {
                         TextView shortcutKeyTextView = (TextView) inflater.inflate(
-                                R.layout.keyboard_shortcuts_key_vertical_bar_view,
+                                R.layout.keyboard_shortcuts_key_separator_view,
                                 shortcutItemsContainer,
                                 false);
                         shortcutItemsContainer.addView(shortcutKeyTextView);
@@ -1123,9 +1123,6 @@
         Drawable shortcutKeyDrawable = null;
         if (info.getBaseCharacter() > Character.MIN_VALUE) {
             shortcutKeyString = String.valueOf(info.getBaseCharacter());
-        } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
-            shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
-            shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
         } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
             shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
         } else {
@@ -1231,28 +1228,35 @@
         mButtonOpenApps = keyboardShortcutsView.findViewById(R.id.shortcut_open_apps);
         mButtonSpecificApp = keyboardShortcutsView.findViewById(R.id.shortcut_specific_app);
 
+        LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById(
+                R.id.keyboard_shortcuts_container);
+
         mButtonSystem.setOnClickListener(v -> {
             setCurrentCategoryIndex(SHORTCUT_SYSTEM_INDEX);
-            populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
-                    R.id.keyboard_shortcuts_container));
+            populateKeyboardShortcutSearchList(shortcutsContainer);
+            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                    R.string.keyboard_shortcut_a11y_filter_system));
         });
 
         mButtonInput.setOnClickListener(v -> {
             setCurrentCategoryIndex(SHORTCUT_INPUT_INDEX);
-            populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
-                    R.id.keyboard_shortcuts_container));
+            populateKeyboardShortcutSearchList(shortcutsContainer);
+            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                    R.string.keyboard_shortcut_a11y_filter_input));
         });
 
         mButtonOpenApps.setOnClickListener(v -> {
             setCurrentCategoryIndex(SHORTCUT_OPENAPPS_INDEX);
-            populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
-                    R.id.keyboard_shortcuts_container));
+            populateKeyboardShortcutSearchList(shortcutsContainer);
+            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                    R.string.keyboard_shortcut_a11y_filter_open_apps));
         });
 
         mButtonSpecificApp.setOnClickListener(v -> {
             setCurrentCategoryIndex(SHORTCUT_SPECIFICAPP_INDEX);
-            populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById(
-                    R.id.keyboard_shortcuts_container));
+            populateKeyboardShortcutSearchList(shortcutsContainer);
+            shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
+                    R.string.keyboard_shortcut_a11y_filter_current_app));
         });
 
         mFullButtonList.add(mButtonSystem);
@@ -1276,12 +1280,12 @@
 
     private int getColorOfTextColorOnAccent() {
         return Utils.getColorAttrDefaultColor(
-                mContext, com.android.internal.R.attr.textColorOnAccent);
+                mContext, com.android.internal.R.attr.materialColorOnPrimary);
     }
 
     private int getColorOfTextColorSecondary() {
         return Utils.getColorAttrDefaultColor(
-                mContext, com.android.internal.R.attr.textColorSecondary);
+                mContext, com.android.internal.R.attr.materialColorOnSurface);
     }
 
     // Create the new data structure for handling the N-to-1 key mapping and other complex case.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 61eaff9..acb00d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -85,7 +85,6 @@
 
     private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
     private final SparseArray<String> mModifierNames = new SparseArray<>();
-    private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
     private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
     // Ordered list of modifiers that are supported. All values in this array must exist in
     // mModifierNames.
@@ -340,19 +339,6 @@
         mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
         mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
 
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
-        mSpecialCharacterDrawables.put(
-                KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
-
         mModifierDrawables.put(
                 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
     }
@@ -747,9 +733,6 @@
         Drawable shortcutKeyDrawable = null;
         if (info.getBaseCharacter() > Character.MIN_VALUE) {
             shortcutKeyString = String.valueOf(info.getBaseCharacter());
-        } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
-            shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
-            shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
         } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
             shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index dd24ca7..08415cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1033,7 +1033,7 @@
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
             if (mAlternateBouncerInteractor.isVisibleState()) {
                 return; // udfps affordance is highlighted, no need to show action to unlock
-            } else if (mKeyguardUpdateMonitor.isFaceEnrolled()
+            } else if (mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()
                     && !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) {
                 String message;
                 if (mAccessibilityManager.isEnabled()
@@ -1215,7 +1215,7 @@
                             mContext.getString(R.string.keyguard_suggest_fingerprint)
                     );
                 } else if (fpAuthFailed
-                        && mKeyguardUpdateMonitor.getUserUnlockedWithFace(getCurrentUser())) {
+                        && mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()) {
                     // face had already previously unlocked the device, so instead of showing a
                     // fingerprint error, tell them they have already unlocked with face auth
                     // and how to enter their device
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index ae765e4..49c729e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -160,8 +160,15 @@
     var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null
 
     /** The touch helper responsible for the drag down animation. */
-    val touchHelper = DragDownHelper(falsingManager, falsingCollector, this,
-            naturalScrollingSettingObserver, context)
+    val touchHelper =
+        DragDownHelper(
+            falsingManager,
+            falsingCollector,
+            this,
+            naturalScrollingSettingObserver,
+            shadeRepository,
+            context
+        )
 
     private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy {
         splitShadeOverScrollerFactory.create({ qS }, { nsslController })
@@ -756,6 +763,7 @@
     private val falsingCollector: FalsingCollector,
     private val dragDownCallback: LockscreenShadeTransitionController,
     private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver,
+    private val shadeRepository: ShadeRepository,
     context: Context
 ) : Gefingerpoken {
 
@@ -808,8 +816,9 @@
                 startingChild = null
                 initialTouchY = y
                 initialTouchX = x
-                isTrackpadReverseScroll = !naturalScrollingSettingObserver.isNaturalScrollingEnabled
-                        && isTrackpadScroll(true, event)
+                isTrackpadReverseScroll =
+                    !naturalScrollingSettingObserver.isNaturalScrollingEnabled &&
+                        isTrackpadScroll(true, event)
             }
             MotionEvent.ACTION_MOVE -> {
                 val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
@@ -875,6 +884,7 @@
                     }
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
+                    shadeRepository.setLegacyLockscreenShadeTracking(false)
                 } else {
                     stopDragging()
                     return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 0e83c78..e486457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -530,16 +530,12 @@
                 userHandle = mCurrentUserId;
             }
             if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
-                // default value before moving to 'released'
-                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
-                updateUserShowPrivateSettings(userHandle);
+                Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
+                return false;
             }
             if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
-                // default value before moving to 'released'
-                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
-                updateDpcSettings(userHandle);
+                Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+                return false;
             }
             return mUsersUsersAllowingPrivateNotifications.get(userHandle)
                     && mUsersDpcAllowingPrivateNotifications.get(userHandle);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index b46b525..a3adea0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar.connectivity
 
+import android.os.UserManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.AirplaneModeTile
 import com.android.systemui.qs.tiles.BluetoothTile
@@ -27,6 +30,16 @@
 import com.android.systemui.qs.tiles.InternetTile
 import com.android.systemui.qs.tiles.InternetTileNewImpl
 import com.android.systemui.qs.tiles.NfcTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper
+import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -70,6 +83,9 @@
     @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*>
 
     companion object {
+
+        const val AIRPLANE_MODE_TILE_SPEC = "airplane"
+
         /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
         @Provides
         @IntoMap
@@ -84,5 +100,37 @@
             } else {
                 internetTile
             }
+
+        @Provides
+        @IntoMap
+        @StringKey(AIRPLANE_MODE_TILE_SPEC)
+        fun provideAirplaneModeTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(AIRPLANE_MODE_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_airplane_icon_off,
+                        labelRes = R.string.airplane_mode,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+                policy = QSTilePolicy.Restricted(UserManager.DISALLOW_AIRPLANE_MODE),
+            )
+
+        /** Inject AirplaneModeTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(AIRPLANE_MODE_TILE_SPEC)
+        fun provideAirplaneModeTileViewModel(
+            factory: QSTileViewModelFactory.Static<AirplaneModeTileModel>,
+            mapper: AirplaneModeMapper,
+            stateInteractor: AirplaneModeTileDataInteractor,
+            userActionInteractor: AirplaneModeTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(AIRPLANE_MODE_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
index 747efe3..933d0ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
@@ -36,7 +36,8 @@
     /**
      * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode
      * can be requested by apps). See
-     * [com.android.systemui.statusbar.phone.LightsOutNotifController].
+     * [com.android.systemui.statusbar.phone.LegacyLightsOutNotifController] and
+     * [com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor].
      */
     LIGHTS_OUT,
     /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index d1594ef..0415212 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -33,7 +33,7 @@
 
 /**
  * Repository for data that's specific to the status bar **on keyguard**. For data that applies to
- * all status bars, use [StatusBarModeRepository].
+ * all status bars, use [StatusBarModeRepositoryStore].
  */
 interface KeyguardStatusBarRepository {
     /** True if we can show the user switcher on keyguard and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 47994d9..6429815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -25,10 +25,8 @@
 import android.view.WindowInsetsController.Appearance
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
@@ -38,13 +36,10 @@
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import java.io.PrintWriter
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -61,7 +56,7 @@
  * Note: These status bar modes are status bar *window* states that are sent to us from
  * WindowManager, not determined internally.
  */
-interface StatusBarModeRepository {
+interface StatusBarModePerDisplayRepository {
     /**
      * True if the status bar window is showing transiently and will disappear soon, and false
      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -108,16 +103,15 @@
     fun clearTransient()
 }
 
-@SysUISingleton
-class StatusBarModeRepositoryImpl
-@Inject
+class StatusBarModePerDisplayRepositoryImpl
+@AssistedInject
 constructor(
     @Application scope: CoroutineScope,
-    @DisplayId thisDisplayId: Int,
+    @Assisted("displayId") thisDisplayId: Int,
     private val commandQueue: CommandQueue,
     private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
     ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
+) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
 
     private val commandQueueCallback =
         object : CommandQueue.Callbacks {
@@ -166,7 +160,7 @@
             }
         }
 
-    override fun start() {
+    fun start() {
         commandQueue.addCallback(commandQueueCallback)
     }
 
@@ -340,16 +334,7 @@
     )
 }
 
-@Module
-interface StatusBarModeRepositoryModule {
-    @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
-
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBarModeRepositoryImpl::class)
-    fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
-    @Binds
-    @IntoSet
-    fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+@AssistedFactory
+interface StatusBarModePerDisplayRepositoryFactory {
+    fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
new file mode 100644
index 0000000..962cb095
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+import java.io.PrintWriter
+import javax.inject.Inject
+
+interface StatusBarModeRepositoryStore {
+    val defaultDisplay: StatusBarModePerDisplayRepository
+    fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+}
+
+@SysUISingleton
+class StatusBarModeRepositoryImpl
+@Inject
+constructor(
+    @DisplayId private val displayId: Int,
+    factory: StatusBarModePerDisplayRepositoryFactory
+) :
+    StatusBarModeRepositoryStore,
+    CoreStartable,
+    StatusBarInitializer.OnStatusBarViewInitializedListener {
+    override val defaultDisplay = factory.create(displayId)
+
+    override fun forDisplay(displayId: Int) =
+        if (this.displayId == displayId) {
+            defaultDisplay
+        } else {
+            TODO("b/127878649 implement multi-display state management")
+        }
+
+    override fun start() {
+        defaultDisplay.start()
+    }
+
+    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+        defaultDisplay.onStatusBarViewInitialized(component)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        defaultDisplay.dump(pw, args)
+    }
+}
+
+@Module
+interface StatusBarModeRepositoryModule {
+    @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
+
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBarModeRepositoryStore::class)
+    fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+    @Binds
+    @IntoSet
+    fun bindViewInitListener(
+        impl: StatusBarModeRepositoryImpl
+    ): StatusBarInitializer.OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 64970e4..fa2748c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -16,17 +16,19 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.app.tracing.traceSection
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.app.tracing.traceSection
 import javax.inject.Inject
 
 /**
@@ -40,6 +42,7 @@
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
     private val notificationIconAreaController: NotificationIconAreaController,
     private val renderListInteractor: RenderNotificationListInteractor,
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
 ) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
@@ -49,8 +52,14 @@
 
     fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
         traceSection("StackCoordinator.onAfterRenderList") {
-            controller.setNotifStats(calculateNotifStats(entries))
-            if (NotificationIconContainerRefactor.isEnabled) {
+            val notifStats = calculateNotifStats(entries)
+            if (FooterViewRefactor.isEnabled) {
+                activeNotificationsInteractor.setNotifStats(notifStats)
+            }
+            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
+            //  visibility is handled in the new stack.
+            controller.setNotifStats(notifStats)
+            if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
             } else {
                 notificationIconAreaController.updateNotificationIcons(entries)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index fde4ecb..a37937a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -26,6 +26,7 @@
 
 /** Data provided to the NotificationRootController whenever the pipeline runs */
 data class NotifStats(
+    // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag.
     val numActiveNotifs: Int,
     val hasNonClearableAlertingNotifs: Boolean,
     val hasClearableAlertingNotifs: Boolean,
@@ -33,17 +34,16 @@
     val hasClearableSilentNotifs: Boolean
 ) {
     companion object {
-        @JvmStatic
-        val empty = NotifStats(0, false, false, false, false)
+        @JvmStatic val empty = NotifStats(0, false, false, false, false)
     }
 }
 
 /**
  * An implementation of NotifStackController which provides default, no-op implementations of each
- * method.  This is used by ArcSystemUI so that that implementation can opt-in to overriding
- * methods, rather than forcing us to add no-op implementations in their implementation every time
- * a method is added.
+ * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods,
+ * rather than forcing us to add no-op implementations in their implementation every time a method
+ * is added.
  */
 open class DefaultNotifStackController @Inject constructor() : NotifStackController {
     override fun setNotifStats(stats: NotifStats) {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 860ad63..0f14135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -64,6 +64,10 @@
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProviderModule;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
@@ -268,4 +272,24 @@
     /** */
     @Binds
     NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
+
+    /** */
+    @Provides
+    @SysUISingleton
+    static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
+            Provider<NotificationInterruptStateProviderImpl> oldImplProvider,
+            Provider<VisualInterruptionDecisionProviderImpl> newImplProvider) {
+        if (VisualInterruptionRefactor.isEnabled()) {
+            return newImplProvider.get();
+        } else {
+            return new NotificationInterruptStateProviderWrapper(oldImplProvider.get());
+        }
+    }
+
+    /** */
+    @Binds
+    @IntoMap
+    @ClassKey(VisualInterruptionDecisionProvider.class)
+    CoreStartable startVisualInterruptionDecisionProvider(
+            VisualInterruptionDecisionProvider provider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 12ee54d..5ed82cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -37,6 +38,9 @@
 
     /** Are any already-seen notifications currently filtered out of the active list? */
     val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+
+    /** Stats about the list of notifications attached to the shade */
+    val notifStats = MutableStateFlow(NotifStats.empty)
 }
 
 /** Represents the notification list, comprised of groups and individual notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 85ba205..31893b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -15,17 +15,19 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 class ActiveNotificationsInteractor
 @Inject
 constructor(
-    repository: ActiveNotificationListRepository,
+    private val repository: ActiveNotificationListRepository,
 ) {
     /** Notifications actively presented to the user in the notification stack, in order. */
     val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
@@ -40,4 +42,25 @@
                 }
             }
         }
+
+    /** Are any notifications being actively presented in the notification stack? */
+    val areAnyNotificationsPresent: Flow<Boolean> =
+        repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged()
+
+    /**
+     * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous
+     * code.
+     */
+    val areAnyNotificationsPresentValue: Boolean
+        get() = repository.activeNotifications.value.renderList.isNotEmpty()
+
+    /** Are there are any notifications that can be cleared by the "Clear all" button? */
+    val hasClearableNotifications: Flow<Boolean> =
+        repository.notifStats
+            .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs }
+            .distinctUntilChanged()
+
+    fun setNotifStats(notifStats: NotifStats) {
+        repository.notifStats.value = notifStats
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 10a43d5..3184d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 public class FooterView extends StackScrollerDecorView {
     private static final String TAG = "FooterView";
@@ -63,9 +64,13 @@
     private String mSeenNotifsFilteredText;
     private Drawable mSeenNotifsFilteredIcon;
 
+    private @StringRes int mClearAllButtonTextId;
+    private @StringRes int mClearAllButtonDescriptionId;
     private @StringRes int mMessageStringId;
     private @DrawableRes int mMessageIconId;
 
+    private OnClickListener mClearAllButtonClickListener;
+
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -84,12 +89,18 @@
         return isSecondaryVisible();
     }
 
+    /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */
+    public void setClearAllButtonVisible(boolean visible, boolean animate) {
+        setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null);
+    }
+
     /**
      * Set the visibility of the "Clear all" button to {@code visible}. Animate the change if
      * {@code animate} is true.
      */
-    public void setClearAllButtonVisible(boolean visible, boolean animate) {
-        setSecondaryVisible(visible, animate);
+    public void setClearAllButtonVisible(boolean visible, boolean animate,
+            Consumer<Boolean> onAnimationEnded) {
+        setSecondaryVisible(visible, animate, onAnimationEnded);
     }
 
     @Override
@@ -106,6 +117,42 @@
         });
     }
 
+    /** Set the text label for the "Clear all" button. */
+    public void setClearAllButtonText(@StringRes int textId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mClearAllButtonTextId == textId) {
+            return; // nothing changed
+        }
+        mClearAllButtonTextId = textId;
+        updateClearAllButtonText();
+    }
+
+    private void updateClearAllButtonText() {
+        if (mClearAllButtonTextId == 0) {
+            return; // not initialized yet
+        }
+        mClearAllButton.setText(getContext().getString(mClearAllButtonTextId));
+    }
+
+    /** Set the accessibility content description for the "Clear all" button. */
+    public void setClearAllButtonDescription(@StringRes int contentDescriptionId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        if (mClearAllButtonDescriptionId == contentDescriptionId) {
+            return; // nothing changed
+        }
+        mClearAllButtonDescriptionId = contentDescriptionId;
+        updateClearAllButtonDescription();
+    }
+
+    private void updateClearAllButtonDescription() {
+        if (mClearAllButtonDescriptionId == 0) {
+            return; // not initialized yet
+        }
+        mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
+    }
+
     /** Set the string for a message to be shown instead of the buttons. */
     public void setMessageString(@StringRes int messageId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -181,6 +228,10 @@
 
     /** Set onClickListener for the clear all (end) button. */
     public void setClearAllButtonClickListener(OnClickListener listener) {
+        if (FooterViewRefactor.isEnabled()) {
+            if (mClearAllButtonClickListener == listener) return;
+            mClearAllButtonClickListener = listener;
+        }
         mClearAllButton.setOnClickListener(listener);
     }
 
@@ -214,7 +265,28 @@
             mManageButton.setText(mManageNotificationText);
             mManageButton.setContentDescription(mManageNotificationText);
         }
-        if (!FooterViewRefactor.isEnabled()) {
+        if (FooterViewRefactor.isEnabled()) {
+            updateClearAllButtonText();
+            updateClearAllButtonDescription();
+
+            updateMessageString();
+            updateMessageIcon();
+        } else {
+            // NOTE: Prior to the refactor, `updateResources` set the class properties to the right
+            // string values. It was always being called together with `updateContent`, which
+            // deals with actually associating those string values with the correct views
+            // (buttons or text).
+            // In the new code, the resource IDs are being set in the view binder (through
+            // setMessageString and similar setters). The setters themselves now deal with
+            // updating both the resource IDs and the views where appropriate (as in, calling
+            // `updateMessageString` when the resource ID changes). This eliminates the need for
+            // `updateResources`, which will eventually be removed. There are, however, still
+            // situations in which we want to update the views even if the resource IDs didn't
+            // change, such as configuration changes.
+            mClearAllButton.setText(R.string.clear_all_notifications_text);
+            mClearAllButton.setContentDescription(
+                    mContext.getString(R.string.accessibility_clear_all));
+
             mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
             mSeenNotifsFooterTextView
                     .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
@@ -230,16 +302,8 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateColors();
-        mClearAllButton.setText(R.string.clear_all_notifications_text);
-        mClearAllButton.setContentDescription(
-                mContext.getString(R.string.accessibility_clear_all));
         updateResources();
         updateContent();
-
-        if (FooterViewRefactor.isEnabled()) {
-            updateMessageString();
-            updateMessageIcon();
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 6d823437..e0eee96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.notification.footer.ui.viewbinder
 
+import android.view.View
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
@@ -28,14 +32,40 @@
     fun bind(
         footer: FooterView,
         viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
     ): DisposableHandle {
+        // Bind the resource IDs
+        footer.setMessageString(viewModel.message.messageId)
+        footer.setMessageIcon(viewModel.message.iconId)
+        footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
+        footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
+
+        // Bind the click listeners
+        footer.setClearAllButtonClickListener(clearAllNotifications)
+
+        // Listen for visibility changes when the view is attached.
         return footer.repeatWhenAttached {
-            // Listen for changes when the view is attached.
             lifecycleScope.launch {
-                viewModel.message.collect { message ->
-                    footer.setFooterLabelVisible(message.visible)
-                    footer.setMessageString(message.messageId)
-                    footer.setMessageIcon(message.iconId)
+                viewModel.clearAllButton.isVisible.collect { isVisible ->
+                    if (isVisible.isAnimating) {
+                        footer.setClearAllButtonVisible(
+                            isVisible.value,
+                            /* animate = */ true,
+                        ) { _ ->
+                            isVisible.stopAnimating()
+                        }
+                    } else {
+                        footer.setClearAllButtonVisible(
+                            isVisible.value,
+                            /* animate = */ false,
+                        )
+                    }
+                }
+            }
+
+            lifecycleScope.launch {
+                viewModel.message.isVisible.collect { visible ->
+                    footer.setFooterLabelVisible(visible)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 8e55695..244555a 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
-import org.robolectric.annotation.GraphicsMode;
+import android.annotation.StringRes
+import com.android.systemui.util.ui.AnimatedValue
+import kotlinx.coroutines.flow.Flow
+
+data class FooterButtonViewModel(
+    @StringRes val labelId: Int,
+    @StringRes val accessibilityDescriptionId: Int,
+    val isVisible: Flow<AnimatedValue<Boolean>>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
index bc912fb..85cd397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
@@ -18,10 +18,11 @@
 
 import android.annotation.DrawableRes
 import android.annotation.StringRes
+import kotlinx.coroutines.flow.StateFlow
 
 /** A ViewModel for the string message that can be shown in the footer. */
 data class FooterMessageViewModel(
     @StringRes val messageId: Int,
     @DrawableRes val iconId: Int,
-    val visible: Boolean,
+    val isVisible: StateFlow<Boolean>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 7390485..e6b0abc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -18,30 +18,53 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+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.footer.ui.view.FooterView
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
 import javax.inject.Provider
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for [FooterView]. */
-class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) {
-    init {
-        /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
-    }
+class FooterViewModel(
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    seenNotificationsInteractor: SeenNotificationsInteractor,
+    shadeInteractor: ShadeInteractor,
+) {
+    val clearAllButton: FooterButtonViewModel =
+        FooterButtonViewModel(
+            labelId = R.string.clear_all_notifications_text,
+            accessibilityDescriptionId = R.string.accessibility_clear_all,
+            isVisible =
+                activeNotificationsInteractor.hasClearableNotifications
+                    .sample(
+                        combine(
+                                shadeInteractor.isShadeFullyExpanded,
+                                shadeInteractor.isShadeTouchable,
+                                ::Pair
+                            )
+                            .onStart { emit(Pair(false, false)) }
+                    ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) ->
+                        val shouldAnimate = isShadeFullyExpanded && animationsEnabled
+                        AnimatableEvent(hasClearableNotifications, shouldAnimate)
+                    }
+                    .toAnimatedValueFlow(),
+        )
 
-    val message: Flow<FooterMessageViewModel> =
-        seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
-            FooterMessageViewModel(
-                messageId = R.string.unlock_to_see_notif_text,
-                iconId = R.drawable.ic_friction_lock_closed,
-                visible = hasFilteredOutNotifs,
-            )
-        }
+    val message: FooterMessageViewModel =
+        FooterMessageViewModel(
+            messageId = R.string.unlock_to_see_notif_text,
+            iconId = R.drawable.ic_friction_lock_closed,
+            isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications,
+        )
 }
 
 @Module
@@ -49,10 +72,18 @@
     @Provides
     @SysUISingleton
     fun provideOptional(
+        activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
         seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
+        shadeInteractor: Provider<ShadeInteractor>,
     ): Optional<FooterViewModel> {
         return if (FooterViewRefactor.isEnabled) {
-            Optional.of(FooterViewModel(seenNotificationsInteractor.get()))
+            Optional.of(
+                FooterViewModel(
+                    activeNotificationsInteractor.get(),
+                    seenNotificationsInteractor.get(),
+                    shadeInteractor.get()
+                )
+            )
         } else {
             Optional.empty()
         }
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 a85c440..b1e52af 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
@@ -244,7 +244,7 @@
                 }
 
                 // Add and bind.
-                val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings
+                val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
                 for ((idx, notifKey) in toAdd.withIndex()) {
                     // Lookup the StatusBarIconView from the store.
                     val sbiv = viewStore.iconView(notifKey)
@@ -252,6 +252,7 @@
                         failedBindings.add(notifKey)
                         continue
                     }
+                    failedBindings.remove(notifKey)
                     // The view might still be transiently added if it was just removed and added
                     // again
                     view.removeTransientView(sbiv)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9f2b0a6..8e82442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -204,6 +204,11 @@
     override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
 }
 
+class BubbleAppSuspendedSuppressor :
+    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") {
+    override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended
+}
+
 class BubbleNoMetadataSuppressor() :
     VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 2fffd37..334e08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -56,6 +56,14 @@
         }
     }
 
+    fun logSuspendedAppBubble(entry: NotificationEntry) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+        }, {
+            "No bubble up: notification: app $str1 is suspended"
+        })
+    }
+
     fun logNoBubbleNoMetadata(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 4045380..510086d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -204,6 +204,11 @@
             return false;
         }
 
+        if (entry.getRanking().isSuspended()) {
+            mLogger.logSuspendedAppBubble(entry);
+            return false;
+        }
+
         if (entry.getBubbleMetadata() == null
                 || (entry.getBubbleMetadata().getShortcutId() == null
                     && entry.getBubbleMetadata().getIntent() == null)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
index f732e8d..16bcd43d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -31,6 +31,9 @@
 class NotificationInterruptStateProviderWrapper(
     private val wrapped: NotificationInterruptStateProvider
 ) : VisualInterruptionDecisionProvider {
+    init {
+        VisualInterruptionRefactor.assertInLegacyMode()
+    }
 
     @VisibleForTesting
     enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index de8863c..93fa85d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.interruption
 
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 
 /**
@@ -26,7 +27,7 @@
  * pulsing while the device is dozing), displaying the notification as a bubble, and launching a
  * full-screen intent for the notification.
  */
-interface VisualInterruptionDecisionProvider {
+interface VisualInterruptionDecisionProvider : CoreStartable {
     /**
      * Represents the decision to visually interrupt or not.
      *
@@ -53,7 +54,7 @@
     }
 
     /** Initializes the provider. */
-    fun start() {}
+    override fun start() {}
 
     /**
      * Adds a [NotificationInterruptSuppressor] that can suppress visual interruptions.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 2b6e1a1..73dd5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -60,6 +60,10 @@
     private val uiEventLogger: UiEventLogger,
     private val userTracker: UserTracker,
 ) : VisualInterruptionDecisionProvider {
+    init {
+        check(!VisualInterruptionRefactor.isUnexpectedlyInLegacyMode())
+    }
+
     interface Loggable {
         val uiEventId: UiEventEnum?
         val eventLogData: EventLogData?
@@ -155,6 +159,7 @@
         addFilter(PulseLockscreenVisibilityPrivateSuppressor())
         addFilter(PulseLowImportanceSuppressor())
         addFilter(BubbleNotAllowedSuppressor())
+        addFilter(BubbleAppSuspendedSuppressor())
         addFilter(BubbleNoMetadataSuppressor())
         addFilter(HunGroupAlertBehaviorSuppressor())
         addFilter(HunJustLaunchedFsiSuppressor())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 2047c62..ee79727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -73,6 +73,11 @@
     override val uiEventId: UiEventEnum? = null,
     override val eventLogData: EventLogData? = null
 ) : VisualInterruptionSuppressor {
+    constructor(
+        types: Set<VisualInterruptionType>,
+        reason: String
+    ) : this(types, reason, /* uiEventId = */ null)
+
     /** @return true if these interruptions should be suppressed right now. */
     abstract fun shouldSuppress(): Boolean
 }
@@ -84,6 +89,11 @@
     override val uiEventId: UiEventEnum? = null,
     override val eventLogData: EventLogData? = null
 ) : VisualInterruptionSuppressor {
+    constructor(
+        types: Set<VisualInterruptionType>,
+        reason: String
+    ) : this(types, reason, /* uiEventId = */ null)
+
     /**
      * @param entry the notification to consider suppressing
      * @return true if these interruptions should be suppressed for this notification right now
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 11c65e5..6cb079a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2221,8 +2221,8 @@
                     if (mMenuRow != null) {
                         mMenuRow.resetMenu();
                     }
-                    mTranslateAnim = null;
                 }
+                mTranslateAnim = null;
             }
         });
         mTranslateAnim = translateAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index b95e053..8eda96f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -156,7 +156,7 @@
                     new int[]{com.android.internal.R.attr.state_hovered},
                     new int[]{}},
 
-                    new int[]{0, 0, tintColor}
+                    new int[]{tintColor, tintColor, tintColor}
             );
             mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
             mBackground.setTintList(stateList);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index ec90a8d..162e8af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -54,7 +54,7 @@
         mContent = findContentView();
         mSecondaryView = findSecondaryView();
         setVisible(false /* visible */, false /* animate */);
-        setSecondaryVisible(false /* visible */, false /* animate */);
+        setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */);
         setOutlineProvider(null);
     }
 
@@ -155,15 +155,23 @@
     /**
      * Set the secondary view of this layout to visible.
      *
-     * @param visible should the secondary view be visible
-     * @param animate should the change be animated
+     * @param visible          True if the contents should be visible.
+     * @param animate          True if we should fade to new visibility.
+     * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a
+     *                         parameter that represents whether the animation was cancelled.
      */
-    protected void setSecondaryVisible(boolean visible, boolean animate) {
+    protected void setSecondaryVisible(boolean visible, boolean animate,
+            Consumer<Boolean> onAnimationEnded) {
         if (mIsSecondaryVisible != visible) {
             mSecondaryAnimating = animate;
             mIsSecondaryVisible = visible;
-            setViewVisible(mSecondaryView, visible, animate,
-                    (cancelled) -> onSecondaryVisibilityAnimationEnd());
+            Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> {
+                onContentVisibilityAnimationEnd();
+                if (onAnimationEnded != null) {
+                    onAnimationEnded.accept(cancelled);
+                }
+            };
+            setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper);
         }
 
         if (!mSecondaryAnimating) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
index 44387c2..8fc7106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
@@ -50,4 +50,4 @@
      */
     @JvmStatic
     inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 46488c6..3bbdfd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
+import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
@@ -202,9 +203,6 @@
     private final boolean mDebugRemoveAnimation;
     private final boolean mSensitiveRevealAnimEndabled;
     private final RefactorFlag mAnimatedInsets;
-
-    private final boolean mNewAodTransition;
-
     private int mContentHeight;
     private float mIntrinsicContentHeight;
     private int mPaddingBetweenElements;
@@ -567,6 +565,7 @@
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
+    private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
     private boolean mHasFilteredOutSeenNotifications;
     @Nullable private SplitShadeStateController mSplitShadeStateController = null;
     private boolean mIsSmallLandscapeLockscreenEnabled = false;
@@ -633,7 +632,6 @@
         mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled(
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
         mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
-        mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION);
         mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         mAnimatedInsets =
@@ -743,7 +741,9 @@
         updateFooter();
     }
 
-    void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+    /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
+    public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+        FooterViewRefactor.assertInLegacyMode();
         mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
     }
 
@@ -752,7 +752,6 @@
         if (mFooterView == null || mController == null) {
             return;
         }
-        // TODO: move this logic to controller, which will invoke updateFooterView directly
         final boolean showHistory = mController.isHistoryEnabled();
         final boolean showDismissView = shouldShowDismissView();
 
@@ -776,13 +775,6 @@
                 && !mIsRemoteInputActive;
     }
 
-    /**
-     * Return whether there are any clearable notifications
-     */
-    boolean hasActiveClearableNotifications(@SelectedRows int selection) {
-        return mController.hasActiveClearableNotifications(selection);
-    }
-
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -1283,6 +1275,7 @@
      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
      */
     private void updateChildren() {
+        Trace.beginSection("NSSL#updateChildren");
         updateScrollStateForAddedChildren();
         mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
                 ? 0
@@ -1293,6 +1286,7 @@
         } else {
             startAnimationToState();
         }
+        Trace.endSection();
     }
 
     private void onPreDrawDuringAnimation() {
@@ -1373,7 +1367,11 @@
             mTopPadding = topPadding;
             updateAlgorithmHeightAndPadding();
             updateContentHeight();
-            if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+            if (mAmbientState.isOnKeyguard()
+                    && !mShouldUseSplitNotificationShade
+                    && mShouldSkipTopPaddingAnimationAfterFold) {
+                mShouldSkipTopPaddingAnimationAfterFold = false;
+            } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
                 mTopPaddingNeedsAnimation = true;
                 mNeedsAnimation = true;
             }
@@ -1462,7 +1460,7 @@
 
     @VisibleForTesting
     public void updateStackHeight(float endHeight, float fraction) {
-        if (!mNewAodTransition) {
+        if (!newAodTransition()) {
             // During the (AOD<=>LS) transition where dozeAmount is changing,
             // apply dozeAmount to stack height instead of expansionFraction
             // to unfurl notifications on AOD=>LS wakeup (and furl up on LS=>AOD sleep)
@@ -1661,8 +1659,44 @@
     /**
      * @return the position from where the appear transition ends when expanding.
      * Measured in absolute height.
+     *
+     * TODO(b/308591475): This entire logic can probably be improved as part of the empty shade
+     *  refactor, but for now:
+     *  - if the empty shade is visible, we can assume that the visible notif count is not 0;
+     *  - if the shelf is visible, we can assume there are at least 2 notifications present (this
+     *    is not true for AOD, but this logic refers to the expansion of the shade, where we never
+     *    have the shelf on its own)
      */
     private float getAppearEndPosition() {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            return getAppearEndPositionLegacy();
+        }
+
+        int appearPosition = mAmbientState.getStackTopMargin();
+        if (mEmptyShadeView.getVisibility() == GONE) {
+            if (isHeadsUpTransition()
+                    || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
+                if (mShelf.getVisibility() != GONE) {
+                    appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
+                }
+                appearPosition += getTopHeadsUpPinnedHeight()
+                        + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow());
+            } else if (mShelf.getVisibility() != GONE) {
+                appearPosition += mShelf.getIntrinsicHeight();
+            }
+        } else {
+            appearPosition = mEmptyShadeView.getHeight();
+        }
+        return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
+    }
+
+    /**
+     * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't
+     * need to know about that, so we want to phase this out with the footer view refactor.
+     */
+    private float getAppearEndPositionLegacy() {
+        FooterViewRefactor.assertInLegacyMode();
+
         int appearPosition = mAmbientState.getStackTopMargin();
         int visibleNotifCount = mController.getVisibleNotificationCount();
         if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
@@ -1701,7 +1735,8 @@
             // This can't use expansion fraction as that goes only from 0 to 1. Also when
             // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
             // and that makes translation jump immediately.
-            float appearEndPosition = getAppearEndPosition();
+            float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition()
+                    : getAppearEndPositionLegacy();
             float appearStartPosition = getAppearStartPosition();
             float hunAppearFraction = (height - appearStartPosition)
                     / (appearEndPosition - appearStartPosition);
@@ -3723,6 +3758,11 @@
     }
 
     protected boolean isInsideQsHeader(MotionEvent ev) {
+        if (mQsHeader == null) {
+            Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
+            return false;
+        }
+
         mQsHeader.getBoundsOnScreen(mQsHeaderBound);
         /**
          * One-handed mode defines a feature FEATURE_ONE_HANDED of DisplayArea {@link DisplayArea}
@@ -4580,13 +4620,15 @@
         if (mManageButtonClickListener != null) {
             mFooterView.setManageButtonClickListener(mManageButtonClickListener);
         }
-        mFooterView.setClearAllButtonClickListener(v -> {
-            if (mFooterClearAllListener != null) {
-                mFooterClearAllListener.onClearAll();
-            }
-            clearNotifications(ROWS_ALL, true /* closeShade */);
-            footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
-        });
+        if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.setClearAllButtonClickListener(v -> {
+                if (mFooterClearAllListener != null) {
+                    mFooterClearAllListener.onClearAll();
+                }
+                clearNotifications(ROWS_ALL, true /* closeShade */);
+                footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
+            });
+        }
         if (FooterViewRefactor.isEnabled()) {
             updateFooter();
         }
@@ -4602,12 +4644,21 @@
         addView(mEmptyShadeView, index);
     }
 
-    void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+    /** Legacy version, should be removed with the footer refactor flag. */
+    public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+        FooterViewRefactor.assertInLegacyMode();
+        updateEmptyShadeView(visible, areNotificationsHiddenInShade,
+                mHasFilteredOutSeenNotifications);
+    }
+
+    /** Trigger an update for the empty shade resources and visibility. */
+    public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
+            boolean hasFilteredOutSeenNotifications) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         if (areNotificationsHiddenInShade) {
             updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
-        } else if (mHasFilteredOutSeenNotifications) {
+        } else if (hasFilteredOutSeenNotifications) {
             updateEmptyShadeView(
                     R.string.no_unseen_notif_text,
                     R.string.unlock_to_see_notif_text,
@@ -4650,9 +4701,9 @@
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        mFooterView.setClearAllButtonVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
         if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.setClearAllButtonVisible(showDismissView, animate);
             mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
         }
     }
@@ -4784,10 +4835,13 @@
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
-        if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
-            mController.updateShowEmptyShadeView();
-            updateFooter();
-            mController.updateImportantForAccessibility();
+        if (!FooterViewRefactor.isEnabled()) {
+            // A notification was removed, and we're not currently showing the empty shade view.
+            if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
+                mController.updateShowEmptyShadeView();
+                updateFooter();
+                mController.updateImportantForAccessibility();
+            }
         }
 
         updateSpeedBumpIndex();
@@ -4796,10 +4850,13 @@
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
-        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
-            mController.updateShowEmptyShadeView();
-            updateFooter();
-            mController.updateImportantForAccessibility();
+        if (!FooterViewRefactor.isEnabled()) {
+            // A notification was added, and we're currently showing the empty shade view.
+            if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+                mController.updateShowEmptyShadeView();
+                updateFooter();
+                mController.updateImportantForAccessibility();
+            }
         }
 
         updateSpeedBumpIndex();
@@ -4809,7 +4866,9 @@
         Assert.isMainThread();
         ensureRemovedFromTransientContainer(v);
         addView(v, index);
-        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+        // A notification was added, and we're currently showing the empty shade view.
+        if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
+                && mController.isShowingEmptyShadeView()) {
             mController.updateShowEmptyShadeView();
             updateFooter();
             mController.updateImportantForAccessibility();
@@ -5082,7 +5141,8 @@
         if (mEmptyShadeView.getVisibility() == GONE) {
             return getMinExpansionHeight();
         } else {
-            return getAppearEndPosition();
+            return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
+                    : getAppearEndPositionLegacy();
         }
     }
 
@@ -5309,11 +5369,15 @@
         return viewsToRemove;
     }
 
+    /** Clear all clearable notifications when the user requests it. */
+    public void clearAllNotifications() {
+        clearNotifications(ROWS_ALL, /* closeShade = */ true);
+    }
+
     /**
      * Collects a list of visible rows, and animates them away in a staggered fashion as if they
      * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
      */
-    @VisibleForTesting
     void clearNotifications(@SelectedRows int selection, boolean closeShade) {
         // Animate-swipe all dismissable notifications, then animate the shade closed
         final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
@@ -5613,6 +5677,7 @@
     }
 
     void setFooterClearAllListener(FooterClearAllListener listener) {
+        FooterViewRefactor.assertInLegacyMode();
         mFooterClearAllListener = listener;
     }
 
@@ -5683,6 +5748,7 @@
         boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
         if (split != mShouldUseSplitNotificationShade) {
             mShouldUseSplitNotificationShade = split;
+            mShouldSkipTopPaddingAnimationAfterFold = true;
             mAmbientState.setUseSplitShade(split);
             updateDismissBehavior();
             updateUseRoundedRectClipping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3e140a4..e6315fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -20,7 +20,6 @@
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import static com.android.app.animation.Interpolators.STANDARD;
-
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -108,7 +107,9 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+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.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -223,7 +224,9 @@
                 @Override
                 public void onViewAttachedToWindow(View v) {
                     mConfigurationController.addCallback(mConfigurationListener);
-                    mZenModeController.addCallback(mZenModeControllerCallback);
+                    if (!FooterViewRefactor.isEnabled()) {
+                        mZenModeController.addCallback(mZenModeControllerCallback);
+                    }
                     final int newBarState = mStatusBarStateController.getState();
                     if (newBarState != mBarState) {
                         mStateListener.onStateChanged(newBarState);
@@ -236,7 +239,9 @@
                 @Override
                 public void onViewDetachedFromWindow(View v) {
                     mConfigurationController.removeCallback(mConfigurationListener);
-                    mZenModeController.removeCallback(mZenModeControllerCallback);
+                    if (!FooterViewRefactor.isEnabled()) {
+                        mZenModeController.removeCallback(mZenModeControllerCallback);
+                    }
                     mStatusBarStateController.removeCallback(mStateListener);
                 }
             };
@@ -292,7 +297,9 @@
     final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onDensityOrFontScaleChanged() {
-            updateShowEmptyShadeView();
+            if (!FooterViewRefactor.isEnabled()) {
+                updateShowEmptyShadeView();
+            }
             mView.reinflateViews();
         }
 
@@ -308,7 +315,9 @@
             mView.updateBgColor();
             mView.updateDecorViews();
             mView.reinflateViews();
-            updateShowEmptyShadeView();
+            if (!FooterViewRefactor.isEnabled()) {
+                updateShowEmptyShadeView();
+            }
             updateFooter();
         }
 
@@ -357,7 +366,9 @@
                     mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
                             mLockscreenUserManager.isAnyProfilePublicMode());
                     mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
-                    updateImportantForAccessibility();
+                    if (!FooterViewRefactor.isEnabled()) {
+                        updateImportantForAccessibility();
+                    }
                 }
             };
 
@@ -459,7 +470,7 @@
 
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
-                                     NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
@@ -581,7 +592,7 @@
 
                 @Override
                 public boolean updateSwipeProgress(View animView, boolean dismissable,
-                                                   float swipeProgress) {
+                        float swipeProgress) {
                     // Returning true prevents alpha fading.
                     return false;
                 }
@@ -673,6 +684,7 @@
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
+            ActiveNotificationsInteractor activeNotificationsInteractor,
             SeenNotificationsInteractor seenNotificationsInteractor,
             NotificationListViewBinder viewBinder,
             ShadeController shadeController,
@@ -747,8 +759,10 @@
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
                 NotificationPanelEvent.fromSelection(selection)));
-        mView.setFooterClearAllListener(() ->
-                mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+        if (!FooterViewRefactor.isEnabled()) {
+            mView.setFooterClearAllListener(() ->
+                    mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+        }
         mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
         mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
             @Override
@@ -840,8 +854,10 @@
 
         mViewBinder.bind(mView, this);
 
-        collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
-                this::onKeyguardTransitionChanged);
+        if (!FooterViewRefactor.isEnabled()) {
+            collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
+                    this::onKeyguardTransitionChanged);
+        }
     }
 
     private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1031,6 +1047,8 @@
     }
 
     public int getVisibleNotificationCount() {
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer
+        //  visibility in the refactored code
         return mNotifStats.getNumActiveNotifs();
     }
 
@@ -1074,7 +1092,7 @@
     }
 
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-                                    boolean cancelAnimators) {
+            boolean cancelAnimators) {
         mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
     }
 
@@ -1124,6 +1142,7 @@
     }
 
     public void setQsFullScreen(boolean fullScreen) {
+        FooterViewRefactor.assertInLegacyMode();
         mView.setQsFullScreen(fullScreen);
         updateShowEmptyShadeView();
     }
@@ -1273,7 +1292,10 @@
     public void updateVisibility(boolean visible) {
         mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
 
-        if (mView.getVisibility() == View.VISIBLE) {
+        // Refactor note: the empty shade's visibility doesn't seem to actually depend on the
+        // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not
+        // modeled in the refactored code.
+        if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) {
             // Synchronize EmptyShadeView visibility with the parent container.
             updateShowEmptyShadeView();
             updateImportantForAccessibility();
@@ -1288,6 +1310,8 @@
      * are true.
      */
     public void updateShowEmptyShadeView() {
+        FooterViewRefactor.assertInLegacyMode();
+
         Trace.beginSection("NSSLC.updateShowEmptyShadeView");
 
         final boolean shouldShow = getVisibleNotificationCount() == 0
@@ -1331,6 +1355,7 @@
      * auto-scrolling in NSSL.
      */
     public void updateImportantForAccessibility() {
+        FooterViewRefactor.assertInLegacyMode();
         if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
             mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         } else {
@@ -1338,16 +1363,6 @@
         }
     }
 
-    /**
-     * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
-     * and false otherwise.
-     */
-    private boolean isInTransitionToKeyguard() {
-        final int currentState = mStatusBarStateController.getState();
-        final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState();
-        return (currentState != upcomingState && upcomingState == KEYGUARD);
-    }
-
     public boolean isShowingEmptyShadeView() {
         return mView.isEmptyShadeViewVisible();
     }
@@ -1395,10 +1410,14 @@
      * Return whether there are any clearable notifications
      */
     public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+        //  visibility in the refactored code
         return hasNotifications(selection, true /* clearable */);
     }
 
     public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+        //  visibility in the refactored code
         boolean hasAlertingMatchingClearable = isClearable
                 ? mNotifStats.getHasClearableAlertingNotifs()
                 : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1437,7 +1456,7 @@
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
-                                             boolean remoteInputActive) {
+                    boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 updateFooter();
@@ -1556,7 +1575,7 @@
     }
 
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
-                                @SelectedRows int selectedRows) {
+            @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
@@ -1648,7 +1667,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-                                         int bottomRadius) {
+            int bottomRadius) {
         mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
     }
 
@@ -1678,6 +1697,7 @@
 
     @VisibleForTesting
     void onKeyguardTransitionChanged(TransitionStep transitionStep) {
+        FooterViewRefactor.assertInLegacyMode();
         boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
                 && (transitionStep.getFrom().equals(KeyguardState.GONE)
                 || transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
@@ -2003,11 +2023,21 @@
     private class NotifStackControllerImpl implements NotifStackController {
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
+            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
+            // is handled in the refactored stack.
             mNotifStats = notifStats;
-            mView.setHasFilteredOutSeenNotifications(
-                    mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
+
+            if (!FooterViewRefactor.isEnabled()) {
+                mView.setHasFilteredOutSeenNotifications(
+                        mSeenNotificationsInteractor
+                                .getHasFilteredOutSeenNotifications().getValue());
+            }
+
             updateFooter();
-            updateShowEmptyShadeView();
+
+            if (!FooterViewRefactor.isEnabled()) {
+                updateShowEmptyShadeView();
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
new file mode 100644
index 0000000..abf09ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.data.repository
+
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** A repository which holds state about and controlling the appearance of the notification stack */
+@SysUISingleton
+class NotificationStackAppearanceRepository @Inject constructor() {
+    /** The bounds of the notification stack in the current scene. */
+    val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f))
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
new file mode 100644
index 0000000..32e4e89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** An interactor which controls the appearance of the NSSL */
+@SysUISingleton
+class NotificationStackAppearanceInteractor
+@Inject
+constructor(
+    private val repository: NotificationStackAppearanceRepository,
+) {
+    /** The bounds of the notification stack in the current scene. */
+    val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
+
+    /** Sets the position of the notification stack in the current scene. */
+    fun setStackBounds(bounds: NotificationContainerBounds) {
+        check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
+        repository.stackBounds.value = bounds
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
index 8e55695..5a71bd6 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.stack.shared
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+
+private const val FLEXI_NOTIFS = false
+
+/**
+ * Returns whether flexiglass is displaying notifications, which is currently an optional piece of
+ * flexiglass
+ */
+fun SceneContainerFlags.flexiNotifsEnabled() = FLEXI_NOTIFS && isEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index a5b87f0..a4e1a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,13 +17,19 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import android.view.LayoutInflater
+import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.traceSection
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.reinflateAndBindLatest
+import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationShelf
+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.footer.ui.viewbinder.FooterViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
@@ -36,17 +42,22 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
 class NotificationListViewBinder
 @Inject
 constructor(
     private val viewModel: NotificationListViewModel,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val configuration: ConfigurationState,
     private val configurationController: ConfigurationController,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
     private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
+    private val metricsLogger: MetricsLogger,
     private val shelfIconViewStore: ShelfNotificationIconViewStore,
 ) {
 
@@ -55,8 +66,20 @@
         viewController: NotificationStackScrollLayoutController
     ) {
         bindShelf(view)
-        bindFooter(view)
         bindHideList(viewController, viewModel)
+
+        if (FooterViewRefactor.isEnabled) {
+            bindFooter(view)
+            bindEmptyShade(view)
+
+            view.repeatWhenAttached {
+                lifecycleScope.launch {
+                    viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
+                        view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+                    }
+                }
+            }
+        }
     }
 
     private fun bindShelf(parentView: NotificationStackScrollLayout) {
@@ -85,9 +108,20 @@
                     R.layout.status_bar_notification_footer,
                     parentView,
                     attachToRoot = false,
+                    backgroundDispatcher,
                 ) { footerView: FooterView ->
                     traceSection("bind FooterView") {
-                        val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel)
+                        val disposableHandle =
+                            FooterViewBinder.bind(
+                                footerView,
+                                footerViewModel,
+                                clearAllNotifications = {
+                                    metricsLogger.action(
+                                        MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+                                    )
+                                    parentView.clearAllNotifications()
+                                },
+                            )
                         parentView.setFooterView(footerView)
                         return@reinflateAndBindLatest disposableHandle
                     }
@@ -95,4 +129,26 @@
             }
         }
     }
+
+    private fun bindEmptyShade(
+        parentView: NotificationStackScrollLayout,
+    ) {
+        parentView.repeatWhenAttached {
+            lifecycleScope.launch {
+                combine(
+                        viewModel.shouldShowEmptyShadeView,
+                        viewModel.areNotificationsHiddenInShade,
+                        viewModel.hasFilteredOutSeenNotifications,
+                        ::Triple
+                    )
+                    .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
+                        parentView.updateEmptyShadeView(
+                            shouldShow,
+                            areNotifsHidden,
+                            hasFilteredNotifs,
+                        )
+                    }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
new file mode 100644
index 0000000..fa7a8fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object NotificationStackAppearanceViewBinder {
+
+    @JvmStatic
+    fun bind(
+        view: SharedNotificationContainer,
+        viewModel: NotificationStackAppearanceViewModel,
+        sceneContainerFlags: SceneContainerFlags,
+        ambientState: AmbientState,
+        controller: NotificationStackScrollLayoutController,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    viewModel.stackBounds.collect { bounds ->
+                        controller.updateTopPadding(
+                            bounds.top,
+                            controller.isAddOrRemoveAnimationPending
+                        )
+                    }
+                }
+                launch {
+                    viewModel.expandFraction.collect { expandFraction ->
+                        ambientState.expansionFraction = expandFraction
+                        controller.expandedHeight = expandFraction * controller.view.height
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 0ff1bec..5e60b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -19,8 +19,10 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import kotlinx.coroutines.DisposableHandle
@@ -33,6 +35,7 @@
     fun bind(
         view: SharedNotificationContainer,
         viewModel: SharedNotificationContainerViewModel,
+        sceneContainerFlags: SceneContainerFlags,
         controller: NotificationStackScrollLayoutController,
         notificationStackSizeCalculator: NotificationStackSizeCalculator,
     ): DisposableHandle {
@@ -68,10 +71,13 @@
                             .collect { controller.setMaxDisplayedNotifications(it) }
                     }
 
-                    launch {
-                        viewModel.position.collect {
-                            val animate = it.animate || controller.isAddOrRemoveAnimationPending()
-                            controller.updateTopPadding(it.top, animate)
+                    if (!sceneContainerFlags.flexiNotifsEnabled()) {
+                        launch {
+                            viewModel.bounds.collect {
+                                val animate =
+                                    it.isAnimated || controller.isAddOrRemoveAnimationPending
+                                controller.updateTopPadding(it.top, animate)
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 4f76680..569ae24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,10 +16,22 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+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.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for the list of notifications. */
 class NotificationListViewModel
@@ -27,5 +39,78 @@
 constructor(
     val shelf: NotificationShelfViewModel,
     val hideListViewModel: HideListViewModel,
-    val footer: Optional<FooterViewModel>
-)
+    val footer: Optional<FooterViewModel>,
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    seenNotificationsInteractor: SeenNotificationsInteractor,
+    shadeInteractor: ShadeInteractor,
+    zenModeInteractor: ZenModeInteractor,
+) {
+    /**
+     * We want the NSSL to be unimportant for accessibility when there are no notifications in it
+     * while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise,
+     * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL.
+     * See b/242235264 for more details.
+     */
+    val isImportantForAccessibility: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(true)
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    keyguardTransitionInteractor.isFinishedInStateWhere {
+                        KeyguardState.lockscreenVisibleInState(it)
+                    }
+                ) { hasNotifications, isOnKeyguard ->
+                    hasNotifications || !isOnKeyguard
+                }
+                .distinctUntilChanged()
+        }
+    }
+
+    val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    shadeInteractor.isQsFullscreen,
+                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
+                        emit(false)
+                    },
+                    keyguardTransitionInteractor
+                        .isFinishedInState(KeyguardState.PRIMARY_BOUNCER)
+                        .onStart { emit(false) }
+                ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing ->
+                    !hasNotifications &&
+                        !isQsFullScreen &&
+                        // Hide empty shade view when in transition to AOD.
+                        // That avoids "No Notifications" blinking when transitioning to AOD.
+                        // For more details, see b/228790482.
+                        !transitioningToAOD &&
+                        // Don't show any notification content if the bouncer is showing. See
+                        // b/267060171.
+                        !isBouncerShowing
+                }
+                .distinctUntilChanged()
+        }
+    }
+
+    // TODO(b/308591475): This should be tracked separately by the empty shade.
+    val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            zenModeInteractor.areNotificationsHiddenInShade
+        }
+    }
+
+    // TODO(b/308591475): This should be tracked separately by the empty shade.
+    val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            seenNotificationsInteractor.hasFilteredOutSeenNotifications
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
new file mode 100644
index 0000000..f4c0e92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.notification.stack.ui.viewmodel
+
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
+@SysUISingleton
+class NotificationStackAppearanceViewModel
+@Inject
+constructor(
+    stackAppearanceInteractor: NotificationStackAppearanceInteractor,
+    shadeInteractor: ShadeInteractor,
+) {
+    /** The expansion fraction from the top of the notification shade. */
+    val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+
+    /** The bounds of the notification stack in the current scene. */
+    val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
new file mode 100644
index 0000000..c6fd98e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.notification.stack.ui.viewmodel
+
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import javax.inject.Inject
+
+/**
+ * ViewModel used by the Notification placeholders inside the scene container to update the
+ * [NotificationStackAppearanceInteractor], and by extension control the NSSL.
+ */
+@SysUISingleton
+class NotificationsPlaceholderViewModel
+@Inject
+constructor(
+    private val interactor: NotificationStackAppearanceInteractor,
+    flags: SceneContainerFlags,
+    featureFlags: FeatureFlagsClassic,
+) {
+    /** DEBUG: whether the placeholder "Notifications" text should be shown. */
+    val isPlaceholderTextVisible: Boolean = !flags.flexiNotifsEnabled()
+
+    /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
+    val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
+
+    /** DEBUG: whether the debug logging should be output. */
+    val isDebugLoggingEnabled: Boolean = flags.flexiNotifsEnabled()
+
+    /**
+     * Notifies that the bounds of the notification placeholder have changed.
+     *
+     * @param top The position of the top of the container in its window coordinate system, in
+     *   pixels.
+     * @param bottom The position of the bottom of the container in its window coordinate system, in
+     *   pixels.
+     */
+    fun onBoundsChanged(
+        top: Float,
+        bottom: Float,
+    ) {
+        interactor.setStackBounds(NotificationContainerBounds(top, bottom))
+    }
+}
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 d6b6f75..1febaf9 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
@@ -15,9 +15,12 @@
  *
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -25,20 +28,25 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
 class SharedNotificationContainerViewModel
 @Inject
 constructor(
     private val interactor: SharedNotificationContainerInteractor,
+    @Application applicationScope: CoroutineScope,
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
@@ -103,32 +111,38 @@
      *
      * When the shade is expanding, the position is controlled by... the shade.
      */
-    val position: Flow<SharedNotificationContainerPosition> =
-        isOnLockscreenWithoutShade.flatMapLatest { onLockscreen ->
-            if (onLockscreen) {
-                combine(
-                    keyguardInteractor.sharedNotificationContainerPosition,
-                    configurationBasedDimensions
-                ) { position, config ->
-                    if (config.useSplitShade) {
-                        position.copy(top = 0f)
-                    } else {
-                        position
+    val bounds: StateFlow<NotificationContainerBounds> =
+        isOnLockscreenWithoutShade
+            .flatMapLatest { onLockscreen ->
+                if (onLockscreen) {
+                    combine(
+                        keyguardInteractor.notificationContainerBounds,
+                        configurationBasedDimensions
+                    ) { bounds, config ->
+                        if (config.useSplitShade) {
+                            bounds.copy(top = 0f)
+                        } else {
+                            bounds
+                        }
+                    }
+                } else {
+                    interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map {
+                        (top, qsExpansion) ->
+                        // When QS expansion > 0, it should directly set the top padding so do not
+                        // animate it
+                        val animate = qsExpansion == 0f
+                        keyguardInteractor.notificationContainerBounds.value.copy(
+                            top = top,
+                            isAnimated = animate
+                        )
                     }
                 }
-            } else {
-                interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map {
-                    (top, qsExpansion) ->
-                    // When QS expansion > 0, it should directly set the top padding so do not
-                    // animate it
-                    val animate = qsExpansion == 0f
-                    keyguardInteractor.sharedNotificationContainerPosition.value.copy(
-                        top = top,
-                        animate = animate
-                    )
-                }
             }
-        }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = NotificationContainerBounds(0f, 0f),
+            )
 
     /**
      * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
@@ -156,15 +170,11 @@
         // When to limit notifications: on lockscreen with an unexpanded shade. Also, recalculate
         // when the notification stack has changed internally
         val limitedNotifications =
-            combineTransform(
-                isOnLockscreen,
-                position,
-                shadeInteractor.shadeExpansion,
+            combine(
+                bounds,
                 interactor.notificationStackChanged.onStart { emit(Unit) },
-            ) { isOnLockscreen, position, shadeExpansion, _ ->
-                if (isOnLockscreen && shadeExpansion == 0f) {
-                    emit(calculateSpace(position.bottom - position.top))
-                }
+            ) { position, _ ->
+                calculateSpace(position.bottom - position.top)
             }
 
         // When to show unlimited notifications: When the shade is fully expanded and the user is
@@ -178,11 +188,14 @@
                     emit(-1)
                 }
             }
-
-        return merge(
-                limitedNotifications,
-                unlimitedNotifications,
-            )
+        return isOnLockscreenWithoutShade
+            .flatMapLatest { isOnLockscreenWithoutShade ->
+                if (isOnLockscreenWithoutShade) {
+                    limitedNotifications
+                } else {
+                    unlimitedNotifications
+                }
+            }
             .distinctUntilChanged()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index e1fba2e..7aa7976 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -372,26 +372,6 @@
         )
     }
 
-    override fun executeRunnableDismissingKeyguard(
-        runnable: Runnable?,
-        cancelAction: Runnable?,
-        dismissShade: Boolean,
-        afterKeyguardGone: Boolean,
-        deferred: Boolean,
-        willAnimateOnKeyguard: Boolean,
-        customMessage: String?,
-    ) {
-        activityStarterInternal.executeRunnableDismissingKeyguard(
-            runnable = runnable,
-            cancelAction = cancelAction,
-            dismissShade = dismissShade,
-            afterKeyguardGone = afterKeyguardGone,
-            deferred = deferred,
-            willAnimateOnKeyguard = willAnimateOnKeyguard,
-            customMessage = customMessage,
-        )
-    }
-
     override fun postQSRunnableDismissingKeyguard(runnable: Runnable?) {
         postOnUiThread {
             statusBarStateController.setLeaveOpenOnKeyguardHide(true)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 46675c2..57d49b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -27,12 +27,12 @@
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
+import static com.android.systemui.Flags.lightRevealMigration;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
@@ -199,13 +199,13 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -239,6 +239,8 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -250,8 +252,6 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 
-import dagger.Lazy;
-
 /**
  * A class handling initialization and coordination between some of the key central surfaces in
  * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -387,7 +387,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarInitializer mStatusBarInitializer;
     private final StatusBarWindowController mStatusBarWindowController;
-    private final StatusBarModeRepository mStatusBarModeRepository;
+    private final StatusBarModeRepositoryStore mStatusBarModeRepository;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
@@ -457,7 +457,7 @@
     private final NotificationGutsManager mGutsManager;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
-    protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
     private final FragmentService mFragmentService;
@@ -605,7 +605,7 @@
             StatusBarInitializer statusBarInitializer,
             StatusBarWindowController statusBarWindowController,
             StatusBarWindowStateController statusBarWindowStateController,
-            StatusBarModeRepository statusBarModeRepository,
+            StatusBarModeRepositoryStore statusBarModeRepository,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             StatusBarSignalPolicy statusBarSignalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
@@ -618,7 +618,7 @@
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
             NotificationGutsManager notificationGutsManager,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             ShadeExpansionStateManager shadeExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
@@ -725,7 +725,7 @@
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mGutsManager = notificationGutsManager;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+        mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
         mDisplayMetrics = displayMetrics;
@@ -899,7 +899,7 @@
         setUpPresenter();
 
         if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
-            mStatusBarModeRepository.showTransient();
+            mStatusBarModeRepository.getDefaultDisplay().showTransient();
         }
         mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
                 result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
@@ -953,7 +953,7 @@
 
             @Override
             public void onKeyguardGoingAwayChanged() {
-                if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+                if (lightRevealMigration()) {
                     // This code path is not used if the KeyguardTransitionRepository is managing
                     // the lightreveal scrim.
                     return;
@@ -1146,9 +1146,10 @@
 
         mDemoModeController.addCallback(mDemoModeCallback);
         mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged);
+                mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
+                this::onTransientShownChanged);
         mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getStatusBarMode(),
+                mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
                 this::updateBarMode);
 
         mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
@@ -1208,7 +1209,7 @@
 
             @Override
             public void hide() {
-                mStatusBarModeRepository.clearTransient();
+                mStatusBarModeRepository.getDefaultDisplay().clearTransient();
             }
         });
 
@@ -1222,7 +1223,7 @@
         });
         mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
 
-        if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+        if (lightRevealMigration()) {
             LightRevealScrimViewBinder.bind(
                     mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
         }
@@ -1656,7 +1657,7 @@
         if (mDemoModeController.isInDemoMode()) return;
         if (mStatusBarTransitions != null) {
             checkBarMode(
-                    mStatusBarModeRepository.getStatusBarMode().getValue(),
+                    mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(),
                     mStatusBarWindowState,
                     mStatusBarTransitions);
         }
@@ -1667,7 +1668,8 @@
     /** Temporarily hides Bubbles if the status bar is hidden. */
     @Override
     public void updateBubblesVisibility() {
-        StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
+        StatusBarMode mode =
+                mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue();
         mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
                 mode != StatusBarMode.LIGHTS_OUT
                         && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT
@@ -2355,7 +2357,7 @@
             return;
         }
 
-        if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+        if (lightRevealMigration()) {
             return;
         }
 
@@ -2926,45 +2928,6 @@
         }
     }
 
-    /**
-     * Dismiss the keyguard then execute an action.
-     *
-     * @param action The action to execute after dismissing the keyguard.
-     * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard.
-     * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if
-     *                              we are locked.
-     */
-    private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone,
-            boolean collapsePanel, boolean willAnimateOnKeyguard) {
-        if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
-
-        OnDismissAction onDismissAction = new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                new Thread(() -> {
-                    try {
-                        // The intent we are sending is for the application, which
-                        // won't have permission to immediately start an activity after
-                        // the user switches to home.  We know it is safe to do at this
-                        // point, so make sure new activity switches are now allowed.
-                        ActivityManager.getService().resumeAppSwitches();
-                    } catch (RemoteException e) {
-                    }
-                    action.run();
-                }).start();
-
-                return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
-            }
-
-            @Override
-            public boolean willRunAnimationOnKeyguard() {
-                return willAnimateOnKeyguard;
-            }
-        };
-        mActivityStarter.dismissKeyguardThenExecute(onDismissAction, /* cancel= */ null,
-                afterKeyguardGone);
-    }
-
     private void clearNotificationEffects() {
         try {
             mBarService.clearNotificationEffects();
@@ -2992,7 +2955,7 @@
     // End Extra BaseStatusBarMethods.
 
     boolean isTransientShown() {
-        return mStatusBarModeRepository.isTransientShown().getValue();
+        return mStatusBarModeRepository.getDefaultDisplay().isTransientShown().getValue();
     }
 
     private void updateLightRevealScrimVisibility() {
@@ -3001,7 +2964,7 @@
             return;
         }
 
-        if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+        if (!lightRevealMigration()) {
             mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
         }
     }
@@ -3156,7 +3119,7 @@
 
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
-                    if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
+                    if (!lightRevealMigration()
                             && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
                         mLightRevealScrim.setRevealAmount(1f - linear);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b0183d3..a3d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.ListenersTracing.forEachTraced
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -98,7 +99,7 @@
                 FACE_UNLOCK_BYPASS_NEVER -> false
                 else -> field
             }
-            return enabled && mKeyguardStateController.isFaceEnrolled &&
+            return enabled && mKeyguardStateController.isFaceEnrolledAndEnabled &&
                     isPostureAllowedForFaceAuth()
         }
         private set(value) {
@@ -186,7 +187,9 @@
                 }
         }
 
-    private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) }
+    private fun notifyListeners() = listeners.forEachTraced("KeyguardBypassController") {
+        it.onBypassStateChanged(bypassEnabled)
+    }
 
     /**
      * Notify that the biometric unlock has happened.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 3329844..32b3ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -22,7 +22,6 @@
 import android.hardware.TriggerEvent
 import android.hardware.TriggerEventListener
 import com.android.keyguard.ActiveUnlockConfig
-import com.android.keyguard.FaceAuthApiRequestReason
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.CoreStartable
@@ -77,9 +76,6 @@
             isListening = false
             updateListeningState()
             keyguardFaceAuthInteractor.onDeviceLifted()
-            keyguardUpdateMonitor.requestFaceAuth(
-                FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
-            )
             keyguardUpdateMonitor.requestActiveUnlock(
                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
                 "KeyguardLiftController")
@@ -117,7 +113,8 @@
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
-        val shouldListen = (onKeyguard || bouncerVisible) && keyguardUpdateMonitor.isFaceEnrolled
+        val isFaceEnabled = keyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+        val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
         if (shouldListen != isListening) {
             isListening = shouldListen
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
index eba7fe0..7c871e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
@@ -36,6 +36,7 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.util.ViewController;
 
@@ -51,7 +52,7 @@
  * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
  */
 @StatusBarFragmentScope
-public class LightsOutNotifController extends ViewController<View> {
+public class LegacyLightsOutNotifController extends ViewController<View> {
     private final CommandQueue mCommandQueue;
     private final NotifLiveDataStore mNotifDataStore;
     private final WindowManager mWindowManager;
@@ -63,7 +64,7 @@
     private int mDisplayId;
 
     @Inject
-    LightsOutNotifController(
+    LegacyLightsOutNotifController(
             @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView,
             WindowManager windowManager,
             NotifLiveDataStore notifDataStore,
@@ -72,7 +73,12 @@
         mWindowManager = windowManager;
         mNotifDataStore = notifDataStore;
         mCommandQueue = commandQueue;
+    }
 
+    @Override
+    protected void onInit() {
+        super.onInit();
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 1f9952a..a62a1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.flags.Flags.NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.newAodTransition;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -106,9 +106,6 @@
     private NotificationIconContainer mAodIcons;
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
-
-    private final boolean mNewAodTransition;
-
     private int mAodIconAppearTranslation;
 
     private boolean mAnimationsEnabled;
@@ -145,7 +142,6 @@
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
-        mNewAodTransition = featureFlags.isEnabled(NEW_AOD_TRANSITION);
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
         mDozeParameters = dozeParameters;
@@ -600,7 +596,7 @@
         boolean animate = true;
         if (!mBypassController.getBypassEnabled()) {
             animate = mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking();
-            if (!mNewAodTransition) {
+            if (!newAodTransition()) {
                 // We only want the appear animations to happen when the notifications get fully
                 // hidden, since otherwise the unhide animation overlaps
                 animate &= fullyHidden;
@@ -640,7 +636,7 @@
             mAodIconsVisible = visible;
             mAodIcons.animate().cancel();
             if (animate) {
-                if (mNewAodTransition) {
+                if (newAodTransition()) {
                     // Let's make sure the icon are translated to 0, since we cancelled it above
                     animateInAodIconTranslation();
                     if (mAodIconsVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 4d3e2ad..eec617b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -42,7 +42,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.kotlin.JavaAdapter;
@@ -68,7 +68,7 @@
     private final JavaAdapter mJavaAdapter;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final StatusBarModeRepository mStatusBarModeRepository;
+    private final StatusBarModeRepositoryStore mStatusBarModeRepository;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
@@ -126,7 +126,7 @@
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            StatusBarModeRepository statusBarModeRepository,
+            StatusBarModeRepositoryStore statusBarModeRepository,
             DumpManager dumpManager,
             DisplayTracker displayTracker) {
         mJavaAdapter = javaAdapter;
@@ -146,7 +146,7 @@
     @Override
     public void start() {
         mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getStatusBarAppearance(),
+                mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
                 this::onStatusBarAppearanceChanged);
     }
 
@@ -476,7 +476,7 @@
         private final DarkIconDispatcher mDarkIconDispatcher;
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
-        private final StatusBarModeRepository mStatusBarModeRepository;
+        private final StatusBarModeRepositoryStore mStatusBarModeRepository;
         private final DumpManager mDumpManager;
         private final DisplayTracker mDisplayTracker;
 
@@ -486,7 +486,7 @@
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
-                StatusBarModeRepository statusBarModeRepository,
+                StatusBarModeRepositoryStore statusBarModeRepository,
                 DumpManager dumpManager,
                 DisplayTracker displayTracker) {
             mJavaAdapter = javaAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 3e753a5..ae04eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -396,9 +396,6 @@
             states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
         }
 
-        mScrimBehind.setDefaultFocusHighlightEnabled(false);
-        mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
-        mScrimInFront.setDefaultFocusHighlightEnabled(false);
         mTransparentScrimBackground = notificationsScrim.getResources()
                 .getBoolean(R.bool.notification_scrim_transparent);
         updateScrims();
@@ -509,12 +506,6 @@
 
         applyState();
 
-        // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
-        // We need to disable focus otherwise AOD would end up with a gray overlay.
-        mScrimInFront.setFocusable(!state.isLowPowerState());
-        mScrimBehind.setFocusable(!state.isLowPowerState());
-        mNotificationsScrim.setFocusable(!state.isLowPowerState());
-
         mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor());
 
         // Cancel blanking transitions that were pending before we requested a new state
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 274b50f..daadedb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1636,13 +1636,6 @@
     }
 
     /**
-     * Request to authenticate using face.
-     */
-    public void requestFace(boolean request) {
-        mKeyguardUpdateManager.requestFaceAuthOnOccludingApp(request);
-    }
-
-    /**
      * Request to authenticate using the fingerprint sensor.  If the fingerprint sensor is udfps,
      * uses the color provided by udfpsColor for the fingerprint icon.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index dbee080..2e1a077 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -72,7 +72,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
@@ -115,7 +114,6 @@
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final com.android.systemui.shade.ShadeController mShadeController;
     private final KeyguardStateController mKeyguardStateController;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final LockPatternUtils mLockPatternUtils;
     private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
     private final ActivityIntentHelper mActivityIntentHelper;
@@ -154,7 +152,6 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             ShadeController shadeController,
             KeyguardStateController keyguardStateController,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
             LockPatternUtils lockPatternUtils,
             StatusBarRemoteInputCallback remoteInputCallback,
             ActivityIntentHelper activityIntentHelper,
@@ -187,7 +184,6 @@
         mLockscreenUserManager = lockscreenUserManager;
         mShadeController = shadeController;
         mKeyguardStateController = keyguardStateController;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 07e2571..8e9c038 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
 import static com.android.systemui.statusbar.phone.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED;
 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
 
@@ -29,6 +32,8 @@
 import android.util.Slog;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.InitController;
 import com.android.systemui.dagger.SysUISingleton;
@@ -54,7 +59,10 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
@@ -63,6 +71,8 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.Set;
+
 import javax.inject.Inject;
 
 @SysUISingleton
@@ -163,7 +173,14 @@
         initController.addPostInitTask(() -> {
             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
-            visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor);
+            if (VisualInterruptionRefactor.isEnabled()) {
+                visualInterruptionDecisionProvider.addCondition(mAlertsDisabledCondition);
+                visualInterruptionDecisionProvider.addCondition(mVrModeCondition);
+                visualInterruptionDecisionProvider.addFilter(mNeedsRedactionFilter);
+                visualInterruptionDecisionProvider.addCondition(mPanelsDisabledCondition);
+            } else {
+                visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor);
+            }
             mLockscreenUserManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(
                     this, mNotifListContainer, mOnSettingsClickListener);
@@ -306,4 +323,54 @@
             return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
         }
     };
+
+    private final VisualInterruptionCondition mAlertsDisabledCondition =
+            new VisualInterruptionCondition(Set.of(PEEK, PULSE, BUBBLE),
+                    "notification alerts disabled") {
+                @Override
+                public boolean shouldSuppress() {
+                    return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
+                }
+            };
+
+    private final VisualInterruptionCondition mVrModeCondition =
+            new VisualInterruptionCondition(Set.of(PEEK, BUBBLE), "device is in VR mode") {
+                @Override
+                public boolean shouldSuppress() {
+                    return isDeviceInVrMode();
+                }
+            };
+
+    private final VisualInterruptionFilter mNeedsRedactionFilter =
+            new VisualInterruptionFilter(Set.of(PEEK), "needs redaction on public lockscreen") {
+                @Override
+                public boolean shouldSuppress(@NonNull NotificationEntry entry) {
+                    if (!mKeyguardStateController.isOccluded()) {
+                        return false;
+                    }
+
+                    if (!mLockscreenUserManager.needsRedaction(entry)) {
+                        return false;
+                    }
+
+                    final int currentUserId = mLockscreenUserManager.getCurrentUserId();
+                    final boolean currentUserPublic = mLockscreenUserManager.isLockscreenPublicMode(
+                            currentUserId);
+
+                    final int notificationUserId = entry.getSbn().getUserId();
+                    final boolean notificationUserPublic =
+                            mLockscreenUserManager.isLockscreenPublicMode(notificationUserId);
+
+                    // TODO(b/135046837): we can probably relax this with dynamic privacy
+                    return currentUserPublic || notificationUserPublic;
+                }
+            };
+
+    private final VisualInterruptionCondition mPanelsDisabledCondition =
+            new VisualInterruptionCondition(Set.of(PEEK), "disabled panel") {
+                @Override
+                public boolean shouldSuppress() {
+                    return !mCommandQueue.panelsEnabled();
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt
new file mode 100644
index 0000000..ed8b3e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where status
+ * bar and navigation icons dim. In this mode, a notification dot appears where the notification
+ * icons would appear if they would be shown outside of this mode.
+ *
+ * This interactor knows whether the device is in [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
+ */
+@SysUISingleton
+class LightsOutInteractor
+@Inject
+constructor(private val repository: StatusBarModeRepositoryStore) {
+
+    fun isLowProfile(displayId: Int): Flow<Boolean> =
+        repository.forDisplay(displayId).statusBarMode.map {
+            when (it) {
+                StatusBarMode.LIGHTS_OUT,
+                StatusBarMode.LIGHTS_OUT_TRANSPARENT -> true
+                else -> false
+            }
+        }
+}
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 7adc08c..49880d4 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
@@ -43,7 +43,6 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -139,7 +138,6 @@
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mLocationPublisher;
-    private final FeatureFlagsClassic mFeatureFlags;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
@@ -228,7 +226,6 @@
             StatusBarLocationPublisher locationPublisher,
             NotificationIconAreaController notificationIconAreaController,
             ShadeExpansionStateManager shadeExpansionStateManager,
-            FeatureFlagsClassic featureFlags,
             StatusBarIconController statusBarIconController,
             DarkIconManager.Factory darkIconManagerFactory,
             CollapsedStatusBarViewModel collapsedStatusBarViewModel,
@@ -258,7 +255,6 @@
         mLocationPublisher = locationPublisher;
         mNotificationIconAreaController = notificationIconAreaController;
         mShadeExpansionStateManager = shadeExpansionStateManager;
-        mFeatureFlags = featureFlags;
         mStatusBarIconController = statusBarIconController;
         mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
         mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 0618abb..96faa35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -18,8 +18,9 @@
 
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
@@ -78,7 +79,9 @@
         getBatteryMeterViewController().init();
         getHeadsUpAppearanceController().init();
         getPhoneStatusBarViewController().init();
-        getLightsOutNotifController().init();
+        if (!NotificationsLiveDataStoreRefactor.isEnabled()) {
+            getLegacyLightsOutNotifController().init();
+        }
         getStatusBarDemoMode().init();
     }
 
@@ -101,7 +104,7 @@
 
     /** */
     @StatusBarFragmentScope
-    LightsOutNotifController getLightsOutNotifController();
+    LegacyLightsOutNotifController getLegacyLightsOutNotifController();
 
     /** */
     @StatusBarFragmentScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b0532ce..0bdd1a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -68,7 +68,7 @@
     private val dumpManager: DumpManager,
     private val statusBarWindowController: StatusBarWindowController,
     private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
-    private val statusBarModeRepository: StatusBarModeRepository,
+    private val statusBarModeRepository: StatusBarModeRepositoryStore,
 ) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable {
     private var isFullscreen: Boolean = false
     /** Non-null if there's an active call notification. */
@@ -129,7 +129,7 @@
         dumpManager.registerDumpable(this)
         notifCollection.addCollectionListener(notifListener)
         scope.launch {
-            statusBarModeRepository.isInFullscreenMode.collect {
+            statusBarModeRepository.defaultDisplay.isInFullscreenMode.collect {
                 isFullscreen = it
                 updateChipClickListener()
                 updateGestureListening()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index da9c45a..9c78ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -27,7 +27,7 @@
  *
  * This class is used to break a dependency cycle between
  * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
- * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two
+ * [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two
  * classes both refer to this repository.
  */
 @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 3522b9a..4f702d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -109,8 +109,9 @@
             {
                 int1 = subId
                 str1 = displayInfo.toString()
+                bool1 = displayInfo.isRoaming
             },
-            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" },
+            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1 isRoaming=$bool1" },
         )
     }
 
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 125fd9b..4fb99c24 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
@@ -46,6 +46,8 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
@@ -105,6 +107,7 @@
     private val bgDispatcher: CoroutineDispatcher,
     logger: MobileInputLogger,
     override val tableLogBuffer: TableLogBuffer,
+    flags: FeatureFlagsClassic,
     scope: CoroutineScope,
 ) : MobileConnectionRepository {
     init {
@@ -201,9 +204,15 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isRoaming =
-        callbackEvents
-            .mapNotNull { it.onServiceStateChanged }
-            .map { it.serviceState.roaming }
+        if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) {
+                callbackEvents
+                    .mapNotNull { it.onDisplayInfoChanged }
+                    .map { it.telephonyDisplayInfo.isRoaming }
+            } else {
+                callbackEvents
+                    .mapNotNull { it.onServiceStateChanged }
+                    .map { it.serviceState.roaming }
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val operatorAlphaShort =
@@ -432,6 +441,7 @@
         private val logger: MobileInputLogger,
         private val carrierConfigRepository: CarrierConfigRepository,
         private val mobileMappingsProxy: MobileMappingsProxy,
+        private val flags: FeatureFlagsClassic,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
@@ -456,6 +466,7 @@
                 bgDispatcher,
                 logger,
                 mobileLogger,
+                flags,
                 scope,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index b9b88f4..7d7f49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.binder
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.launch
@@ -61,9 +65,49 @@
                         listener.onTransitionFromLockscreenToDreamStarted()
                     }
                 }
+
+                if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                    val displayId = view.display.displayId
+                    val lightsOutView: View = view.requireViewById(R.id.notification_lights_out)
+                    launch {
+                        viewModel.areNotificationsLightsOut(displayId).collect { show ->
+                            animateLightsOutView(lightsOutView, show)
+                        }
+                    }
+                }
             }
         }
     }
+
+    private fun animateLightsOutView(view: View, visible: Boolean) {
+        view.animate().cancel()
+
+        val alpha = if (visible) 1f else 0f
+        val duration = if (visible) 750L else 250L
+        val visibility = if (visible) View.VISIBLE else View.GONE
+
+        if (visible) {
+            view.alpha = 0f
+            view.visibility = View.VISIBLE
+        }
+
+        view
+            .animate()
+            .alpha(alpha)
+            .setDuration(duration)
+            .setListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        view.alpha = alpha
+                        view.visibility = visibility
+                        // Unset the listener, otherwise this may persist for
+                        // another view property animation
+                        view.animate().setListener(null)
+                    }
+                }
+            )
+            .start()
+    }
 }
 
 /** Listener for various events that may affect the status bar's visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index 15ab143..52a6d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -20,11 +20,17 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
 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.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -48,12 +54,25 @@
 
     /** Emits whenever a transition from lockscreen to dream has started. */
     val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
+
+    /**
+     * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
+     * status bar and navigation icons dim. In this mode, a notification dot appears where the
+     * notification icons would appear if they would be shown outside of this mode.
+     *
+     * This flow tells when to show or hide the notification dot in the status bar to indicate
+     * whether there are notifications when the device is in
+     * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
+     */
+    fun areNotificationsLightsOut(displayId: Int): Flow<Boolean>
 }
 
 @SysUISingleton
 class CollapsedStatusBarViewModelImpl
 @Inject
 constructor(
+    private val lightsOutInteractor: LightsOutInteractor,
+    private val notificationsInteractor: ActiveNotificationsInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     @Application coroutineScope: CoroutineScope,
 ) : CollapsedStatusBarViewModel {
@@ -69,4 +88,17 @@
         keyguardTransitionInteractor.lockscreenToDreamingTransition
             .filter { it.transitionState == TransitionState.STARTED }
             .map {}
+
+    override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> =
+        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+            emptyFlow()
+        } else {
+            combine(
+                    notificationsInteractor.areAnyNotificationsPresent,
+                    lightsOutInteractor.isLowProfile(displayId),
+                ) { hasNotifications, isLowProfile ->
+                    hasNotifications && isLowProfile
+                }
+                .distinctUntilChanged()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
index a32a5ab..8f1ac81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -22,11 +22,14 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.tracing.ListenersTracing;
 import com.android.internal.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.Assert;
 
+import kotlin.Unit;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -75,7 +78,11 @@
             mCurrentDevicePosture =
                     mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
 
-            mListeners.forEach(l -> l.onPostureChanged(mCurrentDevicePosture));
+            ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl",
+                    l -> {
+                        l.onPostureChanged(mCurrentDevicePosture);
+                        return Unit.INSTANCE;
+                    });
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 52133ee..ad2b070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -130,7 +130,7 @@
     /**
      * If there are faces enrolled and user enabled face auth on keyguard.
      */
-    default boolean isFaceEnrolled() {
+    default boolean isFaceEnrolledAndEnabled() {
         return false;
     }
 
@@ -265,7 +265,7 @@
 
         /**
          * Triggered when face auth becomes available or unavailable. Value should be queried with
-         * {@link KeyguardStateController#isFaceEnrolled()}.
+         * {@link KeyguardStateController#isFaceEnrolledAndEnabled()}.
          */
         default void onFaceEnrolledChanged() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 8cc7e7d2..3deb9e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -85,7 +85,7 @@
     private boolean mTrustManaged;
     private boolean mTrusted;
     private boolean mDebugUnlocked = false;
-    private boolean mFaceEnrolled;
+    private boolean mFaceEnrolledAndEnabled;
 
     private float mDismissAmount = 0f;
     private boolean mDismissingFromTouch = false;
@@ -260,16 +260,16 @@
                 || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
         boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
         boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
-        boolean faceEnrolled = mKeyguardUpdateMonitor.isFaceEnrolled(user);
+        boolean faceEnabledAndEnrolled = mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled();
         boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
                 || trustManaged != mTrustManaged || mTrusted != trusted
-                || mFaceEnrolled != faceEnrolled;
+                || mFaceEnrolledAndEnabled != faceEnabledAndEnrolled;
         if (changed || updateAlways) {
             mSecure = secure;
             mCanDismissLockScreen = canDismissLockScreen;
             mTrusted = trusted;
             mTrustManaged = trustManaged;
-            mFaceEnrolled = faceEnrolled;
+            mFaceEnrolledAndEnabled = faceEnabledAndEnrolled;
             mLogger.logKeyguardStateUpdate(
                     mSecure, mCanDismissLockScreen, mTrusted, mTrustManaged);
             notifyUnlockedChanged();
@@ -290,8 +290,8 @@
     }
 
     @Override
-    public boolean isFaceEnrolled() {
-        return mFaceEnrolled;
+    public boolean isFaceEnrolledAndEnabled() {
+        return mFaceEnrolledAndEnabled;
     }
 
     @Override
@@ -416,7 +416,7 @@
         pw.println("  mTrustManaged: " + mTrustManaged);
         pw.println("  mTrusted: " + mTrusted);
         pw.println("  mDebugUnlocked: " + mDebugUnlocked);
-        pw.println("  mFaceEnrolled: " + mFaceEnrolled);
+        pw.println("  mFaceEnrolled: " + mFaceEnrolledAndEnabled);
         pw.println("  isKeyguardFadingAway: " + isKeyguardFadingAway());
         pw.println("  isKeyguardGoingAway: " + isKeyguardGoingAway());
         pw.println("  isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index d66ad58..75ae16e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.policy
 
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.AlarmTile
 import com.android.systemui.qs.tiles.CameraToggleTile
@@ -23,8 +25,22 @@
 import com.android.systemui.qs.tiles.MicrophoneToggleTile
 import com.android.systemui.qs.tiles.UiModeNightTile
 import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper
+import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
+import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper
+import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.IntoMap
 import dagger.multibindings.StringKey
 
@@ -40,6 +56,75 @@
     @StringKey(WorkModeTile.TILE_SPEC)
     fun bindWorkModeTile(workModeTile: WorkModeTile): QSTileImpl<*>
 
+    companion object {
+        const val FLASHLIGHT_TILE_SPEC = "flashlight"
+        const val LOCATION_TILE_SPEC = "location"
+
+        /** Inject flashlight config */
+        @Provides
+        @IntoMap
+        @StringKey(FLASHLIGHT_TILE_SPEC)
+        fun provideFlashlightTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(FLASHLIGHT_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_flashlight_icon_off,
+                        labelRes = R.string.quick_settings_flashlight_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject FlashlightTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(FLASHLIGHT_TILE_SPEC)
+        fun provideFlashlightTileViewModel(
+            factory: QSTileViewModelFactory.Static<FlashlightTileModel>,
+            mapper: FlashlightMapper,
+            stateInteractor: FlashlightTileDataInteractor,
+            userActionInteractor: FlashlightTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(FLASHLIGHT_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+
+        /** Inject location config */
+        @Provides
+        @IntoMap
+        @StringKey(LOCATION_TILE_SPEC)
+        fun provideLocationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(LOCATION_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_location_icon_off,
+                        labelRes = R.string.quick_settings_location_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject LocationTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(LOCATION_TILE_SPEC)
+        fun provideLocationTileViewModel(
+            factory: QSTileViewModelFactory.Static<LocationTileModel>,
+            mapper: LocationTileMapper,
+            stateInteractor: LocationTileDataInteractor,
+            userActionInteractor: LocationTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(LOCATION_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
+
     /** Inject FlashlightTile into tileMap in QSModule */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e4e9554..b598782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -125,14 +125,19 @@
      * is different.
      */
     public void refreshStatusBarHeight() {
-        int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext);
+        Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight");
+        try {
+            int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext);
 
-        if (mBarHeight != heightFromConfig) {
-            mBarHeight = heightFromConfig;
-            apply(mCurrentState);
+            if (mBarHeight != heightFromConfig) {
+                mBarHeight = heightFromConfig;
+                apply(mCurrentState);
+            }
+
+            if (DEBUG) Log.v(TAG, "defineSlots");
+        } finally {
+            Trace.endSection();
         }
-
-        if (DEBUG) Log.v(TAG, "defineSlots");
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5a9f5d5..886fa70 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -19,6 +19,7 @@
 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -71,12 +72,15 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.monet.TonalPalette;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
@@ -127,7 +131,6 @@
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final Handler mBgHandler;
-    private final boolean mIsMonochromaticEnabled;
     private final Context mContext;
     private final boolean mIsMonetEnabled;
     private final boolean mIsFidelityEnabled;
@@ -161,6 +164,8 @@
     private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
     private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final JavaAdapter mJavaAdapter;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final UiModeManager mUiModeManager;
     private DynamicScheme mDynamicSchemeDark;
     private DynamicScheme mDynamicSchemeLight;
@@ -200,8 +205,12 @@
                 return;
             }
             boolean currentUser = userId == mUserTracker.getUserId();
-            if (currentUser && !mAcceptColorEvents
-                    && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
+            boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
+                    ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue(
+                        state -> KeyguardState.Companion.deviceIsAsleepInState(state))
+                    : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
+
+            if (currentUser && !mAcceptColorEvents && isAsleep) {
                 mDeferredWallpaperColors.put(userId, wallpaperColors);
                 mDeferredWallpaperColorsFlags.put(userId, which);
                 Log.i(TAG, "colors received; processing deferred until screen off: "
@@ -395,9 +404,10 @@
             FeatureFlags featureFlags,
             @Main Resources resources,
             WakefulnessLifecycle wakefulnessLifecycle,
+            JavaAdapter javaAdapter,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
             UiModeManager uiModeManager) {
         mContext = context;
-        mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
         mDeviceProvisionedController = deviceProvisionedController;
@@ -412,6 +422,8 @@
         mUserTracker = userTracker;
         mResources = resources;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mUiModeManager = uiModeManager;
         dumpManager.registerDumpable(TAG, this);
     }
@@ -494,21 +506,34 @@
         }
         mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null,
                 UserHandle.USER_ALL);
-        mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
-            @Override
-            public void onFinishedGoingToSleep() {
-                final int userId = mUserTracker.getUserId();
-                final WallpaperColors colors = mDeferredWallpaperColors.get(userId);
-                if (colors != null) {
-                    int flags = mDeferredWallpaperColorsFlags.get(userId);
 
-                    mDeferredWallpaperColors.put(userId, null);
-                    mDeferredWallpaperColorsFlags.put(userId, 0);
+        Runnable whenAsleepHandler = () -> {
+            final int userId = mUserTracker.getUserId();
+            final WallpaperColors colors = mDeferredWallpaperColors.get(userId);
+            if (colors != null) {
+                int flags = mDeferredWallpaperColorsFlags.get(userId);
 
-                    handleWallpaperColors(colors, flags, userId);
-                }
+                mDeferredWallpaperColors.put(userId, null);
+                mDeferredWallpaperColorsFlags.put(userId, 0);
+
+                handleWallpaperColors(colors, flags, userId);
             }
-        });
+        };
+
+        if (themeOverlayControllerWakefulnessDeprecation()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mKeyguardTransitionInteractor.isFinishedInState(KeyguardState.DOZING),
+                    isFinishedInDozing -> {
+                        if (isFinishedInDozing) whenAsleepHandler.run();
+                    });
+        } else {
+            mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onFinishedGoingToSleep() {
+                    whenAsleepHandler.run();
+                }
+            });
+        }
     }
 
     private void reevaluateSystemTheme(boolean forceReload) {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 1482cfc..10fc83c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
-import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
 import com.android.systemui.util.kotlin.getOrNull
 import dagger.BindsInstance
+import dagger.Lazy
 import dagger.Module
 import dagger.Provides
 import dagger.Subcomponent
@@ -32,10 +34,7 @@
 import javax.inject.Named
 import javax.inject.Scope
 
-@Scope
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class SysUIUnfoldScope
+@Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SysUIUnfoldScope
 
 /**
  * Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with
@@ -57,15 +56,18 @@
         provider: Optional<UnfoldTransitionProgressProvider>,
         rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
         @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
+        @UnfoldBg bgProvider: Optional<UnfoldTransitionProgressProvider>,
+        unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>,
         factory: SysUIUnfoldComponent.Factory
     ): Optional<SysUIUnfoldComponent> {
         val p1 = provider.getOrNull()
         val p2 = rotationProvider.getOrNull()
         val p3 = scopedProvider.getOrNull()
-        return if (p1 == null || p2 == null || p3 == null) {
+        val p4 = bgProvider.getOrNull()
+        return if (p1 == null || p2 == null || p3 == null || p4 == null) {
             Optional.empty()
         } else {
-            Optional.of(factory.create(p1, p2, p3))
+            Optional.of(factory.create(p1, p2, p3, p4, unfoldLatencyTracker.get()))
         }
     }
 }
@@ -79,7 +81,9 @@
         fun create(
             @BindsInstance p1: UnfoldTransitionProgressProvider,
             @BindsInstance p2: NaturalRotationUnfoldProgressProvider,
-            @BindsInstance p3: ScopedUnfoldTransitionProgressProvider
+            @BindsInstance p3: ScopedUnfoldTransitionProgressProvider,
+            @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider,
+            @BindsInstance p5: UnfoldLatencyTracker,
         ): SysUIUnfoldComponent
     }
 
@@ -98,4 +102,8 @@
     fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
 
     fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager
+
+    fun getUnfoldLatencyTracker(): UnfoldLatencyTracker
+
+    fun getNaturalRotationUnfoldProgressProvider(): NaturalRotationUnfoldProgressProvider
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 36a1e8a..b72c6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -35,6 +35,9 @@
 import android.view.SurfaceSession
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
+import com.android.app.tracing.traceSection
+import com.android.keyguard.logging.ScrimLogger
+import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -45,16 +48,16 @@
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
 import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.app.tracing.traceSection
-import com.android.keyguard.logging.ScrimLogger
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper
 import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
+import javax.inject.Provider
 
 @SysUIUnfoldScope
 class UnfoldLightRevealOverlayAnimation
@@ -65,11 +68,14 @@
     private val deviceStateManager: DeviceStateManager,
     private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
-    private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    @UnfoldBg
+    private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
     private val displayAreaHelper: Optional<DisplayAreaHelper>,
     @Main private val executor: Executor,
     private val threadFactory: ThreadFactory,
-    private val rotationChangeProvider: RotationChangeProvider,
+    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
+    @UnfoldBg private val unfoldProgressHandler: Handler,
     private val displayTracker: DisplayTracker,
     private val scrimLogger: ScrimLogger,
 ) {
@@ -96,11 +102,15 @@
     fun init() {
         // This method will be called only on devices where this animation is enabled,
         // so normally this thread won't be created
-        bgHandler = threadFactory.buildHandlerOnNewThread(TAG)
+        bgHandler = unfoldProgressHandler
         bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
 
         deviceStateManager.registerCallback(bgExecutor, FoldListener())
-        unfoldTransitionProgressProvider.addCallback(transitionListener)
+        if (unfoldAnimationBackgroundProgress()) {
+            unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
+        } else {
+            unfoldTransitionProgressProvider.get().addCallback(transitionListener)
+        }
         rotationChangeProvider.addCallback(rotationWatcher)
 
         val containerBuilder =
@@ -169,8 +179,13 @@
 
         overlayAddReason = reason
 
-        val newRoot = SurfaceControlViewHost(context, context.display!!, wwm,
-                "UnfoldLightRevealOverlayAnimation")
+        val newRoot =
+            SurfaceControlViewHost(
+                context,
+                context.display,
+                wwm,
+                "UnfoldLightRevealOverlayAnimation"
+            )
         val params = getLayoutParams()
         val newView =
             LightRevealScrim(
@@ -353,12 +368,13 @@
     }
 
     private fun executeInBackground(f: () -> Unit) {
-        check(Looper.myLooper() != bgHandler.looper) {
-            "Trying to execute using background handler while already running" +
-                " in the background handler"
+        // This is needed to allow progresses to be received both from the main thread (that will
+        // schedule a runnable on the bg thread), and from the bg thread directly (no reposting).
+        if (bgHandler.looper.isCurrentThread) {
+            f()
+        } else {
+            bgHandler.post(f)
         }
-        // The UiBackground executor is not used as it doesn't have a prepared looper.
-        bgHandler.post(f)
     }
 
     private fun ensureInBackground() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index 12b8845..94912bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -21,11 +21,14 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.unfold.system.DeviceStateRepository
 import com.android.systemui.unfold.updates.FoldStateRepository
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
 
 /**
  * Logs several unfold related details in a trace. Mainly used for debugging and investigate
@@ -37,7 +40,8 @@
 constructor(
     private val context: Context,
     private val foldStateRepository: FoldStateRepository,
-    @Application private val applicationScope: CoroutineScope,
+    @Application applicationScope: CoroutineScope,
+    @Background private val coroutineContext: CoroutineContext,
     private val deviceStateRepository: DeviceStateRepository
 ) : CoreStartable {
     private val isFoldable: Boolean
@@ -46,20 +50,22 @@
                 .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
                 .isNotEmpty()
 
+    private val bgScope = applicationScope.plus(coroutineContext)
+
     override fun start() {
         if (!isFoldable) return
 
-        applicationScope.launch {
+        bgScope.launch {
             val foldUpdateLogger = TraceStateLogger("FoldUpdate")
             foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) }
         }
 
-        applicationScope.launch {
+        bgScope.launch {
             foldStateRepository.hingeAngle.collect {
                 Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
             }
         }
-        applicationScope.launch {
+        bgScope.launch {
             val foldedStateLogger = TraceStateLogger("FoldedState")
             deviceStateRepository.isFolded.collect { isFolded ->
                 foldedStateLogger.log(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 7b628f8..0531487 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -102,7 +103,7 @@
     @Singleton
     fun provideNaturalRotationProgressProvider(
         context: Context,
-        rotationChangeProvider: RotationChangeProvider,
+        @UnfoldMain rotationChangeProvider: RotationChangeProvider,
         unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
     ): Optional<NaturalRotationUnfoldProgressProvider> =
         unfoldTransitionProgressProvider.map { provider ->
@@ -153,7 +154,8 @@
 
         return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider ->
             UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
-        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        }
+            ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 5fc435a..cf76c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.model.SelectedUserModel
@@ -120,7 +119,6 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
-    featureFlags: FeatureFlags,
 ) : UserRepository {
 
     private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt
new file mode 100644
index 0000000..b2297d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.util.drawable
+
+import android.content.res.Resources
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.Drawable
+import androidx.appcompat.graphics.drawable.DrawableWrapperCompat
+
+/**
+ * Create a looped [Animatable2] restarting it when the animation finishes on its own. Calling
+ * [LoopedAnimatable2DrawableWrapper.stop] cancels further looping.
+ */
+class LoopedAnimatable2DrawableWrapper private constructor(private val animatable2: Animatable2) :
+    DrawableWrapperCompat(animatable2 as Drawable), Animatable2 {
+
+    private val loopedCallback = LoopedCallback()
+
+    private var isLoopedCallbackRegistered: Boolean = false
+
+    override fun start() {
+        animatable2.start()
+        setLoopingRegistered(true)
+    }
+
+    override fun stop() {
+        // stop looping if someone stops the animation
+        setLoopingRegistered(false)
+        animatable2.stop()
+    }
+
+    override fun isRunning(): Boolean = animatable2.isRunning
+
+    override fun registerAnimationCallback(callback: Animatable2.AnimationCallback) =
+        animatable2.registerAnimationCallback(callback)
+
+    override fun unregisterAnimationCallback(callback: Animatable2.AnimationCallback): Boolean =
+        animatable2.unregisterAnimationCallback(callback)
+
+    override fun clearAnimationCallbacks() {
+        animatable2.clearAnimationCallbacks()
+        // re-register looped callback to maintain looped behaviour. LoopedCallback is a static
+        // class and it has no extra references, so it doesn't provoke a memory leak.
+        isLoopedCallbackRegistered = false
+        setLoopingRegistered(true)
+    }
+
+    private fun setLoopingRegistered(isLooping: Boolean) {
+        if (isLooping == isLoopedCallbackRegistered) {
+            return
+        }
+        isLoopedCallbackRegistered = isLooping
+        if (isLooping) {
+            animatable2.registerAnimationCallback(loopedCallback)
+        } else {
+            animatable2.unregisterAnimationCallback(loopedCallback)
+        }
+    }
+
+    override fun getConstantState(): ConstantState? =
+        drawable!!.constantState?.let(LoopedAnimatable2DrawableWrapper::LoopedDrawableState)
+
+    companion object {
+
+        /**
+         * Creates [LoopedAnimatable2DrawableWrapper] from a [drawable]. The [drawable] should
+         * implement [Animatable2].
+         *
+         * It supports the following resource tags:
+         * - `<animated-image>`
+         * - `<animated-vector>`
+         */
+        fun fromDrawable(drawable: Drawable): LoopedAnimatable2DrawableWrapper {
+            require(drawable is Animatable2)
+            return LoopedAnimatable2DrawableWrapper(drawable)
+        }
+    }
+
+    private class LoopedCallback : Animatable2.AnimationCallback() {
+
+        override fun onAnimationEnd(drawable: Drawable?) {
+            (drawable as? Animatable2)?.start()
+        }
+    }
+
+    private class LoopedDrawableState(private val nestedState: ConstantState) : ConstantState() {
+
+        override fun newDrawable(): Drawable = fromDrawable(nestedState.newDrawable())
+
+        override fun newDrawable(res: Resources?): Drawable =
+            fromDrawable(nestedState.newDrawable(res))
+
+        override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable =
+            fromDrawable(nestedState.newDrawable(res, theme))
+
+        override fun canApplyTheme(): Boolean = nestedState.canApplyTheme()
+
+        override fun getChangingConfigurations(): Int = nestedState.changingConfigurations
+    }
+}
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 cc9335e..472f0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -14,6 +14,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.plus
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 
@@ -29,6 +30,14 @@
 
     @Provides
     @SysUISingleton
+    @Background
+    fun bgApplicationScope(
+            @Application applicationScope: CoroutineScope,
+            @Background coroutineContext: CoroutineContext,
+    ): CoroutineScope = applicationScope.plus(coroutineContext)
+
+    @Provides
+    @SysUISingleton
     @Main
     @Deprecated(
         "Use @Main CoroutineContext instead",
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d65a69c..9ee3d22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -64,13 +64,13 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.volume.MediaSessions;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +99,7 @@
 @SysUISingleton
 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
     private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
     private static final int DYNAMIC_STREAM_START_INDEX = 100;
@@ -1339,14 +1340,24 @@
 
         private boolean showForSession(Token token) {
             if (mVolumeAdjustmentForRemoteGroupSessions) {
+                if (DEBUG) {
+                    Log.d(TAG, "Volume adjustment for remote group sessions allowed,"
+                            + " showForSession: true");
+                }
                 return true;
             }
             MediaController ctr = new MediaController(mContext, token);
             String packageName = ctr.getPackageName();
             List<RoutingSessionInfo> sessions =
                     mRouter2Manager.getRoutingSessions(packageName);
-
+            if (DEBUG) {
+                Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name "
+                        + packageName);
+            }
             for (RoutingSessionInfo session : sessions) {
+                if (DEBUG) {
+                    Log.d(TAG, "Found routingSessionInfo: " + session);
+                }
                 if (!session.isSystemSession()
                         && session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2132904..5558aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -214,14 +214,12 @@
                 Utils.getColorAttrDefaultColor(
                         this, com.android.internal.R.attr.colorAccentPrimary));
         mKeyguardFaceAuthInteractor.onWalletLaunched();
-        mKeyguardViewManager.requestFace(true);
     }
 
     @Override
     protected void onPause() {
         super.onPause();
         mKeyguardViewManager.requestFp(false, -1);
-        mKeyguardViewManager.requestFace(false);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index e429446..3f76d30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -261,7 +261,7 @@
 
         // GIVEN fingerprint and face are NOT enrolled
         activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
-        `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
 
         // WHEN unlock intent is allowed when NO biometrics are enrolled (0)
@@ -291,7 +291,7 @@
 
         // GIVEN fingerprint and face are both enrolled
         activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
-        `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
 
         // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
@@ -314,7 +314,7 @@
         )
 
         // WHEN fingerprint ONLY enrolled
-        `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
 
         // THEN active unlock triggers allowed on unlock intent
@@ -325,7 +325,7 @@
         )
 
         // WHEN face ONLY enrolled
-        `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
 
         // THEN active unlock triggers allowed on unlock intent
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 2e9b7e8..b403d1d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -125,10 +125,7 @@
                 repository = repository,
             )
 
-        withDeps.featureFlags.apply {
-            set(Flags.REGION_SAMPLING, false)
-            set(Flags.FACE_AUTH_REFACTOR, false)
-        }
+        withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
         underTest =
             ClockEventController(
                 withDeps.keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 6a08eea..adf0ada 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -18,7 +18,6 @@
 
 import static android.view.View.INVISIBLE;
 
-import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT;
 
@@ -179,7 +178,6 @@
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mExecutor = new FakeExecutor(new FakeSystemClock());
         mFakeFeatureFlags = new FakeFeatureFlags();
-        mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
         mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
         mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false);
         mController = new KeyguardClockSwitchController(
@@ -195,6 +193,7 @@
                 mKeyguardUnlockAnimationController,
                 mSecureSettings,
                 mExecutor,
+                mExecutor,
                 mDumpManager,
                 mClockEventController,
                 mLogBuffer,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index d84c2c0..cb26e61 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -157,6 +157,7 @@
         ArgumentCaptor<ContentObserver> observerCaptor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
+        mExecutor.runAllReady();
         verify(mSecureSettings).registerContentObserverForUser(
                 eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
                     anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
@@ -212,6 +213,7 @@
         ArgumentCaptor<ContentObserver> observerCaptor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
+        mExecutor.runAllReady();
         verify(mSecureSettings).registerContentObserverForUser(
                 eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
                     observerCaptor.capture(), eq(UserHandle.USER_ALL));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 225f125..543b291 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.log.SessionTracker
@@ -137,6 +138,7 @@
     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var audioManager: AudioManager
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
@@ -257,7 +259,7 @@
                 telephonyManager,
                 viewMediatorCallback,
                 audioManager,
-                mock(),
+                faceAuthInteractor,
                 mock(),
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
                 mSelectedUserInteractor,
@@ -576,49 +578,12 @@
     }
 
     @Test
-    fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+    fun onSwipeUp_forwardsItToFaceAuthInteractor() {
         val registeredSwipeListener = registeredSwipeListener
-        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
         setupGetSecurityView(SecurityMode.Password)
         registeredSwipeListener.onSwipeUp()
-        verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
-    }
 
-    @Test
-    fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
-        val registeredSwipeListener = registeredSwipeListener
-        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
-        registeredSwipeListener.onSwipeUp()
-        verify(keyguardUpdateMonitor, never())
-            .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
-    }
-
-    @Test
-    fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
-        val registeredSwipeListener = registeredSwipeListener
-        whenever(
-                keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
-            )
-            .thenReturn(true)
-        setupGetSecurityView(SecurityMode.Password)
-        clearInvocations(viewFlipperController)
-        registeredSwipeListener.onSwipeUp()
-        viewControllerImmediately
-        verify(keyguardPasswordViewControllerMock)
-            .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
-    }
-
-    @Test
-    fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
-        val registeredSwipeListener = registeredSwipeListener
-        whenever(
-                keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
-            )
-            .thenReturn(false)
-        setupGetSecurityView(SecurityMode.Password)
-        registeredSwipeListener.onSwipeUp()
-        verify(keyguardPasswordViewControllerMock, never())
-            .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+        verify(faceAuthInteractor).onSwipeUpOnBouncer()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 146715d..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -70,6 +71,7 @@
 
     @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
     @Mock protected FrameLayout mMediaHostContainer;
+    @Mock protected KeyguardStatusAreaView mKeyguardStatusAreaView;
 
     @Before
     public void setup() {
@@ -109,6 +111,8 @@
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
         when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
+        when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
+                .thenReturn(mKeyguardStatusAreaView);
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 948942f..9c3288b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -27,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.AnimatorTestRule;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -40,6 +43,7 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -51,6 +55,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
     @Test
     public void dozeTimeTick_updatesSlice() {
         mController.dozeTimeTick();
@@ -230,4 +237,34 @@
             throw new RuntimeException(e);
         }
     }
+
+    @Test
+    public void statusAreaHeightChange_animatesHeightOutputChange() {
+        // Init & Capture Layout Listener
+        mController.onInit();
+        mController.onViewAttached();
+
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture());
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        // Setup and validate initial height
+        when(mKeyguardStatusView.getHeight()).thenReturn(200);
+        when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10);
+        assertEquals(190, mController.getLockscreenHeight());
+
+        // Trigger Change and validate value unchanged immediately
+        when(mKeyguardStatusAreaView.getHeight()).thenReturn(100);
+        when(mKeyguardStatusView.getHeight()).thenReturn(300);      // Include child height
+        listener.onLayoutChange(mKeyguardStatusAreaView,
+            /* new layout */ 100, 300, 200, 400,
+            /* old layout */ 100, 300, 200, 300);
+        assertEquals(190, mController.getLockscreenHeight());
+
+        // Complete animation, validate height increased
+        mAnimatorTestRule.advanceTimeBy(200);
+        assertEquals(290, mController.getLockscreenHeight());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2b41e080..1ab634c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -23,24 +23,15 @@
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
-import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
-import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
@@ -88,12 +79,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -121,11 +107,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
-import android.view.Display;
-import android.view.DisplayAdjustments;
-import android.view.DisplayInfo;
-
-import androidx.annotation.NonNull;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -143,10 +124,13 @@
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -199,7 +183,6 @@
             TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
-    private static final int FACE_SENSOR_ID = 0;
     private static final int FINGERPRINT_SENSOR_ID = 1;
 
     @Mock
@@ -217,8 +200,6 @@
     @Mock
     private FingerprintManager mFingerprintManager;
     @Mock
-    private FaceManager mFaceManager;
-    @Mock
     private BiometricManager mBiometricManager;
     @Mock
     private PackageManager mPackageManager;
@@ -277,19 +258,18 @@
     @Mock
     private IActivityTaskManager mActivityTaskManager;
     @Mock
-    private WakefulnessLifecycle mWakefulness;
-    @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    @Captor
+    private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
 
-    private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private final int mCurrentUserId = 100;
 
     @Captor
     private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
             mBiometricEnabledCallbackArgCaptor;
-    @Captor
-    private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
 
     // Direct executor
     private final Executor mBackgroundExecutor = Runnable::run;
@@ -303,14 +283,11 @@
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
     private IFingerprintAuthenticatorsRegisteredCallback
             mFingerprintAuthenticatorsRegisteredCallback;
-    private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
-    private FakeDisplayTracker mDisplayTracker;
 
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mDisplayTracker = new FakeDisplayTracker(mContext);
         when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
@@ -358,12 +335,14 @@
 
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         setupBiometrics(mKeyguardUpdateMonitor);
+        mKeyguardUpdateMonitor.setFaceAuthInteractor(mFaceAuthInteractor);
+        verify(mFaceAuthInteractor).registerListener(mFaceAuthenticationListener.capture());
     }
 
     private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor)
             throws RemoteException {
         captureAuthenticatorsRegisteredCallbacks();
-        setupFaceAuth(/* isClass3 */ false);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false);
         setupFingerprintAuth(/* isClass3 */ true);
 
         verify(mBiometricManager)
@@ -387,12 +366,6 @@
     }
 
     private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException {
-        ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
-                ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
-        verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
-        mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-
         ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
                 ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -402,16 +375,6 @@
                 .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
     }
 
-    private void setupFaceAuth(boolean isClass3) throws RemoteException {
-        when(mFaceManager.isHardwareDetected()).thenReturn(true);
-        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
-        mFaceSensorProperties =
-                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3));
-        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-        assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3());
-    }
-
     private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
         when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
@@ -442,28 +405,6 @@
                 true /* resetLockoutRequiresHardwareAuthToken */);
     }
 
-    @NonNull
-    private FaceSensorPropertiesInternal createFaceSensorProperties(
-            boolean supportsFaceDetection, boolean isClass3) {
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        return new FaceSensorPropertiesInternal(
-                FACE_SENSOR_ID /* id */,
-                isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
-                1 /* maxTemplatesAllowed */,
-                componentInfo,
-                FaceSensorProperties.TYPE_UNKNOWN,
-                supportsFaceDetection /* supportsFaceDetection */,
-                true /* supportsSelfIllumination */,
-                false /* resetLockoutRequiresChallenge */);
-    }
-
     @After
     public void tearDown() {
         if (mMockitoSession != null) {
@@ -887,13 +828,9 @@
     }
 
     @Test
-    public void whenDetectFace_biometricDetectCallback() throws RemoteException {
-        ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
-                ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
-
-        givenDetectFace();
-        verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), any());
-        faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false);
+    public void whenDetectFace_biometricDetectCallback() {
+        mFaceAuthenticationListener.getValue().onDetectionStatusChanged(
+                new FaceDetectionStatus(0, 0, false, 0L));
 
         // THEN verify keyguardUpdateMonitorCallback receives a detect callback
         // and NO authenticate callbacks
@@ -926,40 +863,10 @@
     }
 
     @Test
-    public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException {
-        setupFaceAuth(/* isClass3 */ false);
-        setupFingerprintAuth(/* isClass3 */ true);
-
-        // GIVEN primary auth is not required by StrongAuthTracker
-        primaryAuthNotRequiredByStrongAuthTracker();
-
-        // WHEN fingerprint (class 3) is lock out
-        fingerprintErrorTemporaryLockOut();
-
-        // THEN unlocking with face is not allowed
-        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException {
-        setupFaceAuth(/* isClass3 */ true);
-        setupFingerprintAuth(/* isClass3 */ true);
-
-        // GIVEN primary auth is not required by StrongAuthTracker
-        primaryAuthNotRequiredByStrongAuthTracker();
-
-        // WHEN fingerprint (class 3) is lock out
-        fingerprintErrorTemporaryLockOut();
-
-        // THEN unlocking with face is not allowed
-        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                BiometricSourceType.FACE));
-    }
-
-    @Test
     public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException {
-        setupFaceAuth(/* isClass3 */ true);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
+        when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true);
+
         setupFingerprintAuth(/* isClass3 */ true);
 
         // GIVEN primary auth is not required by StrongAuthTracker
@@ -975,7 +882,7 @@
 
     @Test
     public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException {
-        setupFaceAuth(/* isClass3 */ false);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false);
         setupFingerprintAuth(/* isClass3 */ true);
 
         // GIVEN primary auth is not required by StrongAuthTracker
@@ -1056,162 +963,6 @@
     }
 
     @Test
-    public void testTriesToAuthenticate_whenBouncer() {
-        setKeyguardBouncerVisibility(true);
-        verifyFaceAuthenticateCall();
-    }
-
-    @Test
-    public void testNoStartAuthenticate_whenAboutToShowBouncer() {
-        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
-                /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
-
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void testTriesToAuthenticate_whenKeyguard() {
-        keyguardIsVisible();
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        verifyFaceAuthenticateCall();
-        verify(mUiEventLogger).logWithInstanceIdAndPosition(
-                eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
-                eq(0),
-                eq(null),
-                any(),
-                eq(PowerManager.WAKE_REASON_POWER_BUTTON));
-    }
-
-    @Test
-    public void skipsAuthentication_whenStatusBarShadeLocked() {
-        mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        keyguardIsVisible();
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void skipsAuthentication_whenStrongAuthRequired_nonBypass() {
-        lockscreenBypassIsNotAllowed();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning()
-            throws RemoteException {
-        // GIVEN bypass is enabled, face detection is supported
-        lockscreenBypassIsAllowed();
-        supportsFaceDetection();
-        keyguardIsVisible();
-
-        // GIVEN udfps is supported and strong auth required for weak biometrics (face) only
-        givenUdfpsSupported();
-        primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face
-
-        // WHEN the device wakes up
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        // THEN face detect and authenticate are NOT triggered
-        verifyFaceDetectNeverCalled();
-        verifyFaceAuthenticateNeverCalled();
-
-        // THEN biometric help message sent to callback
-        verify(mTestCallback).onBiometricHelp(
-                eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void faceDetect_whenStrongAuthRequiredAndBypass() throws RemoteException {
-        givenDetectFace();
-
-        // FACE detect is triggered, not authenticate
-        verifyFaceDetectCall();
-        verifyFaceAuthenticateNeverCalled();
-
-        // WHEN bouncer becomes visible
-        setKeyguardBouncerVisibility(true);
-        clearInvocations(mFaceManager);
-
-        // THEN face scanning is not run
-        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-        verifyFaceAuthenticateNeverCalled();
-        verifyFaceDetectNeverCalled();
-    }
-
-    @Test
-    public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
-        // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
-        lockscreenBypassIsAllowed();
-        primaryAuthRequiredEncrypted();
-        keyguardIsVisible();
-
-        // WHEN the device wakes up
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        // FACE detect and authenticate are NOT triggered
-        verifyFaceDetectNeverCalled();
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void requestFaceAuth_whenFaceAuthWasStarted_returnsTrue() throws RemoteException {
-        // This satisfies all the preconditions to run face auth.
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        mTestableLooper.processAllMessages();
-
-        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
-                NOTIFICATION_PANEL_CLICKED);
-
-        assertThat(didFaceAuthRun).isTrue();
-    }
-
-    @Test
-    public void requestFaceAuth_whenFaceAuthWasNotStarted_returnsFalse() throws RemoteException {
-        // This ensures face auth won't run.
-        biometricsDisabledForCurrentUser();
-        mTestableLooper.processAllMessages();
-
-        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
-                NOTIFICATION_PANEL_CLICKED);
-
-        assertThat(didFaceAuthRun).isFalse();
-    }
-
-    @Test
-    public void testTriesToAuthenticate_whenAssistant() {
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAssistantVisible(true);
-
-        verifyFaceAuthenticateCall();
-    }
-
-    @Test
-    public void doesNotTryToAuthenticateWhenKeyguardIsNotShowingButOccluded_whenAssistant() {
-        mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
-        mKeyguardUpdateMonitor.setAssistantVisible(true);
-
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
     public void noFpListeningWhenKeyguardIsOccluded_unlessAlternateBouncerShowing() {
         // GIVEN device is awake but occluded
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1257,62 +1008,6 @@
     }
 
     @Test
-    public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        lockscreenBypassIsAllowed();
-        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
-                new ArrayList<>());
-        keyguardIsVisible();
-        verifyFaceAuthenticateCall();
-    }
-
-    @Test
-    public void faceUnlockDoesNotRunWhenDeviceIsGoingToSleepWithAssistantVisible() {
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAssistantVisible(true);
-
-        verifyFaceAuthenticateCall();
-        mTestableLooper.processAllMessages();
-        clearInvocations(mFaceManager);
-
-        // Device going to sleep while assistant is visible
-        mKeyguardUpdateMonitor.handleStartedGoingToSleep(0);
-        mKeyguardUpdateMonitor.handleFinishedGoingToSleep(0);
-        mTestableLooper.moveTimeForward(DEFAULT_CANCEL_SIGNAL_TIMEOUT);
-        mTestableLooper.processAllMessages();
-
-        mKeyguardUpdateMonitor.handleKeyguardReset();
-
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
-        keyguardIsVisible();
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void testNoFaceAuth_whenLockDown() {
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        userDeviceLockDown();
-
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        keyguardIsVisible();
-        mTestableLooper.processAllMessages();
-
-        verifyFaceAuthenticateNeverCalled();
-        verifyFaceDetectNeverCalled();
-    }
-
-    @Test
     public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() {
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
@@ -1329,18 +1024,6 @@
     }
 
     @Test
-    public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
-        // test whether face will be skipped if authenticated, so the value of isClass3Biometric
-        // doesn't matter here
-        mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
-                true /* isClass3Biometric */);
-        setKeyguardBouncerVisibility(true);
-        mTestableLooper.processAllMessages();
-
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
     public void testFaceAndFingerprintLockout_onlyFace() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -1379,7 +1062,11 @@
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
         int user = mSelectedUserInteractor.getSelectedUserId();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(true /* isClass3Biometric */))
+                .thenReturn(true);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
+        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
+
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
@@ -1388,7 +1075,9 @@
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
         int user = mSelectedUserInteractor.getSelectedUserId();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false);
+        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
+
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
 
@@ -1409,22 +1098,19 @@
     }
 
     @Test
-    public void testBiometricsCleared_whenUserSwitches() throws Exception {
+    public void testBiometricsCleared_whenUserSwitches() {
         final BiometricAuthenticated dummyAuthentication =
                 new BiometricAuthenticated(true /* authenticated */, true /* strong */);
-        mKeyguardUpdateMonitor.mUserFaceAuthenticated.put(0 /* user */, dummyAuthentication);
         mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.put(0 /* user */, dummyAuthentication);
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1);
-        assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1);
 
         mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {
         });
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0);
-        assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0);
     }
 
     @Test
-    public void testMultiUserJankMonitor_whenUserSwitches() throws Exception {
+    public void testMultiUserJankMonitor_whenUserSwitches() {
         mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
         verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
         verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
@@ -1432,22 +1118,17 @@
 
     @Test
     public void testMultiUserLockoutChanged_whenUserSwitches() {
-        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT,
-                BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT);
+        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testMultiUserLockoutNotChanged_whenUserSwitches() {
-        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE,
-                BiometricConstants.BIOMETRIC_LOCKOUT_NONE);
+        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE);
     }
 
     private void testMultiUserLockout_whenUserSwitches(
-            @BiometricConstants.LockoutMode int fingerprintLockoutMode,
-            @BiometricConstants.LockoutMode int faceLockoutMode) {
+            @BiometricConstants.LockoutMode int fingerprintLockoutMode) {
         final int newUser = 12;
-        final boolean faceLockOut =
-                faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
         final boolean fpLockOut =
                 fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 
@@ -1455,16 +1136,11 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verifyFaceAuthenticateCall();
         verifyFingerprintAuthenticateCall();
 
         when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
                 .thenReturn(fingerprintLockoutMode);
-        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
-                .thenReturn(faceLockoutMode);
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
         final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
         mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
         KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
         mKeyguardUpdateMonitor.registerCallback(callback);
@@ -1472,17 +1148,13 @@
         mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
         mTestableLooper.processAllMessages();
 
-        // THEN face and fingerprint listening are always cancelled immediately
-        verify(faceCancel).cancel();
-        verify(callback).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
+        // THEN fingerprint listening are always cancelled immediately
         verify(fpCancel).cancel();
         verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FINGERPRINT));
 
         // THEN locked out states are updated
         assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut);
-        assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut);
 
         // Fingerprint should be cancelled on lockout if going to lockout state, else
         // restarted if it's not
@@ -1752,11 +1424,11 @@
     public void testShouldNotListenForUdfps_whenFaceAuthenticated() {
         // GIVEN a "we should listen for udfps" state
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
+        when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true);
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
         // WHEN face authenticated
-        mKeyguardUpdateMonitor.onFaceAuthenticated(
-                mSelectedUserInteractor.getSelectedUserId(), false);
+        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1777,51 +1449,6 @@
     }
 
     @Test
-    public void testShouldNotUpdateBiometricListeningStateOnStatusBarStateChange() {
-        // GIVEN state for face auth should run aside from StatusBarState
-        biometricsNotDisabledThroughDevicePolicyManager();
-        mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
-        setKeyguardBouncerVisibility(false /* isVisible */);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        lockscreenBypassIsAllowed();
-        keyguardIsVisible();
-
-        // WHEN status bar state reports a change to the keyguard that would normally indicate to
-        // start running face auth
-        mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isEqualTo(true);
-
-        // THEN face unlock is not running b/c status bar state changes don't cause biometric
-        // listening state to update
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
-
-        // WHEN biometric listening state is updated when showing state changes from false => true
-        mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
-
-        // THEN face unlock is running
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true);
-    }
-
-    @Test
-    public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
-
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
-    }
-
-    @Test
-    public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
-
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
-
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
-
-        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
-    }
-
-    @Test
     public void testRequireUnlockForNfc_Broadcast() {
         KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
         mKeyguardUpdateMonitor.registerCallback(callback);
@@ -1833,13 +1460,6 @@
     }
 
     @Test
-    public void testFaceDoesNotAuth_afterPinAttempt() {
-        mTestableLooper.processAllMessages();
-        mKeyguardUpdateMonitor.setCredentialAttempted();
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
     public void testShowTrustGrantedMessage_onTrustGranted() {
         // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
@@ -1855,366 +1475,17 @@
     }
 
     @Test
-    public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
-        cleanupKeyguardUpdateMonitor();
-        mFaceManager = null;
-
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        occludingAppRequestsFaceAuth();
-        currentUserIsSystem();
-        primaryAuthNotRequiredByStrongAuthTracker();
-        biometricsEnabledForCurrentUser();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        // Fingerprint is locked out.
-        fingerprintErrorTemporaryLockOut();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        bouncerFullyVisibleAndNotGoingToSleep();
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        primaryAuthNotRequiredByStrongAuthTracker();
-        biometricsEnabledForCurrentUser();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        userNotCurrentlySwitching();
-
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        triggerSuccessfulFaceAuth();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        bouncerFullyVisibleAndNotGoingToSleep();
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        primaryAuthNotRequiredByStrongAuthTracker();
-        biometricsEnabledForCurrentUser();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        userNotCurrentlySwitching();
-
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        successfulFingerprintAuth();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
-        cleanupKeyguardUpdateMonitor();
-        // This disables face auth
-        when(mUserManager.isSystemUser()).thenReturn(false);
-        mKeyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(mContext);
-
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        primaryAuthNotRequiredByStrongAuthTracker();
-        biometricsEnabledForCurrentUser();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        biometricsEnabledForCurrentUser();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        userNotCurrentlySwitching();
-
-        // This disables face auth
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
-            throws RemoteException {
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        // This disables face auth
-        biometricsDisabledForCurrentUser();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        userCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
-            throws RemoteException {
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        secureCameraLaunched();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
-    public void shouldListenForFace_secureCameraLaunchedButAlternateBouncerIsLaunched_returnsTrue()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        secureCameraLaunched();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        alternateBouncerVisible();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        bouncerFullyVisibleAndNotGoingToSleep();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue()
-            throws RemoteException {
-        // Face auth should run when the following is true.
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        triggerAuthInterrupt();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenKeyguardIsAwake_returnsTrue() throws RemoteException {
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-
-        statusBarShadeIsLocked();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        deviceNotGoingToSleep();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-        deviceIsInteractive();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-        keyguardIsVisible();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-        statusBarShadeIsNotLocked();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenUdfpsFingerDown_returnsTrue() throws RemoteException {
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_whenAlternateBouncerIsShowing_returnsTrue()
-            throws RemoteException {
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_alternateBouncerShowingButDeviceGoingToSleep_returnsFalse()
-            throws RemoteException {
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        deviceNotGoingToSleep();
-        alternateBouncerVisible();
-        mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        deviceGoingToSleep();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    private void alternateBouncerVisible() {
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
-    }
-
-    @Test
-    public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
-            throws RemoteException {
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
-        mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        // Face is locked out.
-        faceAuthLockOut();
-        mTestableLooper.processAllMessages();
-
-        // This is needed beccause we want to show face locked out error message whenever face auth
-        // is supposed to run.
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
     public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verifyFaceAuthenticateCall();
         verifyFingerprintAuthenticateCall();
 
-        mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
+        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
+        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
+                .thenReturn(false);
         // Make sure keyguard is going away after face auth attempt, and that it calls
         // updateBiometricStateListeningState.
         mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
@@ -2234,34 +1505,6 @@
     }
 
     @Test
-    public void testDreamingStopped_faceDoesNotRun() {
-        mKeyguardUpdateMonitor.dispatchDreamingStopped();
-        mTestableLooper.processAllMessages();
-
-        verifyFaceAuthenticateNeverCalled();
-    }
-
-    @Test
-    public void testFaceWakeupTrigger_runFaceAuth_onlyOnConfiguredTriggers() {
-        // keyguard is visible
-        keyguardIsVisible();
-
-        // WHEN device wakes up from an application
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_APPLICATION);
-        mTestableLooper.processAllMessages();
-
-        // THEN face auth isn't triggered
-        verifyFaceAuthenticateNeverCalled();
-
-        // WHEN device wakes up from the power button
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        // THEN face auth is triggered
-        verifyFaceAuthenticateCall();
-    }
-
-    @Test
     public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
         // GIVEN device is interactive
         deviceIsInteractive();
@@ -2422,18 +1665,15 @@
     }
 
     @Test
-    public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() {
-        // GIVEN device is listening for face and fingerprint
+    public void testStrongAuthChange_lockDown_stopsFpListeningState() {
+        // GIVEN device is listening for fingerprint
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verifyFaceAuthenticateCall();
         verifyFingerprintAuthenticateCall();
 
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
         final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
         mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
         KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
         mKeyguardUpdateMonitor.registerCallback(callback);
@@ -2445,88 +1685,13 @@
                 mSelectedUserInteractor.getSelectedUserId());
         mTestableLooper.processAllMessages();
 
-        // THEN face and fingerprint listening are cancelled
-        verify(faceCancel).cancel();
-        verify(callback).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
+        // THEN fingerprint listening are cancelled
         verify(fpCancel).cancel();
         verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FINGERPRINT));
     }
 
     @Test
-    public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() {
-        // GIVEN device is listening for face and fingerprint
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-
-        verifyFaceAuthenticateCall();
-
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        // WHEN non-strong biometric allowed changes
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(
-                mSelectedUserInteractor.getSelectedUserId());
-        mTestableLooper.processAllMessages();
-
-        // THEN face and fingerprint listening are cancelled
-        verify(faceCancel).cancel();
-        verify(callback).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void testPostureChangeToUnsupported_stopsFaceListeningState() {
-        // GIVEN device is listening for face
-        mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED;
-        deviceInPostureStateClosed();
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-
-        verifyFaceAuthenticateCall();
-
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        // WHEN device is opened
-        deviceInPostureStateOpened();
-        mTestableLooper.processAllMessages();
-
-        // THEN face listening is stopped.
-        verify(faceCancel).cancel();
-        verify(callback).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void testShouldListenForFace_withLockedDown_returnsFalse()
-            throws RemoteException {
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        supportsFaceDetection();
-        mTestableLooper.processAllMessages();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        userDeviceLockDown();
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-    }
-
-    @Test
     public void assistantVisible_requestActiveUnlock() {
         // GIVEN active unlock requests from the assistant are allowed
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2549,8 +1714,7 @@
     }
 
     @Test
-    public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
-            throws RemoteException {
+    public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         bouncerFullyVisible();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
@@ -2571,8 +1735,7 @@
     }
 
     @Test
-    public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard()
-            throws RemoteException {
+    public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         keyguardIsVisible();
@@ -2588,7 +1751,8 @@
 
         // WHEN face fails & bypass is not allowed
         lockscreenBypassIsNotAllowed();
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+        mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged(
+                new FailedFaceAuthenticationStatus());
 
         // THEN request unlock with NO keyguard dismissal
         verify(mTrustManager).reportUserRequestedUnlock(
@@ -2597,10 +1761,10 @@
     }
 
     @Test
-    public void faceBypassFailure_requestActiveUnlock_dismissKeyguard()
-            throws RemoteException {
+    public void faceBypassFailure_requestActiveUnlock_dismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true);
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
@@ -2614,7 +1778,8 @@
 
         // WHEN face fails & bypass is not allowed
         lockscreenBypassIsAllowed();
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+        mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged(
+                new FailedFaceAuthenticationStatus());
 
         // THEN request unlock with a keyguard dismissal
         verify(mTrustManager).reportUserRequestedUnlock(
@@ -2623,10 +1788,10 @@
     }
 
     @Test
-    public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard()
-            throws RemoteException {
+    public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true);
         lockscreenBypassIsNotAllowed();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
                 true);
@@ -2638,7 +1803,8 @@
 
         // WHEN face fails & on the bouncer
         bouncerFullyVisible();
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+        mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged(
+                new FailedFaceAuthenticationStatus());
 
         // THEN request unlock with a keyguard dismissal
         verify(mTrustManager).reportUserRequestedUnlock(
@@ -2647,54 +1813,6 @@
     }
 
     @Test
-    public void testShouldListenForFace_withAuthSupportPostureConfig_returnsTrue()
-            throws RemoteException {
-        mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED;
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        supportsFaceDetection();
-
-        deviceInPostureStateOpened();
-        mTestableLooper.processAllMessages();
-        // Should not listen for face when posture state in DEVICE_POSTURE_OPENED
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-
-        deviceInPostureStateClosed();
-        mTestableLooper.processAllMessages();
-        // Should listen for face when posture state in DEVICE_POSTURE_CLOSED
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
-    public void testShouldListenForFace_withoutAuthSupportPostureConfig_returnsTrue()
-            throws RemoteException {
-        mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_UNKNOWN;
-        keyguardNotGoingAway();
-        bouncerFullyVisibleAndNotGoingToSleep();
-        currentUserIsSystem();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        supportsFaceDetection();
-
-        deviceInPostureStateClosed();
-        mTestableLooper.processAllMessages();
-        // Whether device in any posture state, always listen for face
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        deviceInPostureStateOpened();
-        mTestableLooper.processAllMessages();
-        // Whether device in any posture state, always listen for face
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-    }
-
-    @Test
     public void testBatteryChangedIntent_refreshBatteryInfo() {
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(mContext, getBatteryIntent());
 
@@ -2727,8 +1845,7 @@
     }
 
     @Test
-    public void unfoldWakeup_requestActiveUnlock_forceDismissKeyguard()
-            throws RemoteException {
+    public void unfoldWakeup_requestActiveUnlock_forceDismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
@@ -2754,8 +1871,7 @@
     }
 
     @Test
-    public void unfoldWakeup_requestActiveUnlock_noDismissKeyguard()
-            throws RemoteException {
+    public void unfoldWakeup_requestActiveUnlock_noDismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
@@ -2781,8 +1897,7 @@
     }
 
     @Test
-    public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard()
-            throws RemoteException {
+    public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
@@ -2809,8 +1924,7 @@
 
 
     @Test
-    public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard()
-            throws RemoteException {
+    public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
         when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
@@ -2859,44 +1973,6 @@
     }
 
     @Test
-    public void faceAuthenticateOptions_bouncerAuthenticateReason() {
-        // GIVEN the bouncer is fully visible
-        bouncerFullyVisible();
-
-        // WHEN authenticate is called
-        ArgumentCaptor<FaceAuthenticateOptions> captor =
-                ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
-
-        // THEN the authenticate reason is attributed to the bouncer
-        assertThat(captor.getValue().getAuthenticateReason())
-                .isEqualTo(AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN);
-    }
-
-    @Test
-    public void faceAuthenticateOptions_wakingUpAuthenticateReason_powerButtonWakeReason() {
-        // GIVEN keyguard is visible
-        keyguardIsVisible();
-
-        // WHEN device wakes up from the power button
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        // THEN face auth is triggered
-        ArgumentCaptor<FaceAuthenticateOptions> captor =
-                ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
-
-        // THEN the authenticate reason is attributed to the waking
-        assertThat(captor.getValue().getAuthenticateReason())
-                .isEqualTo(AUTHENTICATE_REASON_STARTED_WAKING_UP);
-
-        // THEN the wake reason is attributed to the power button
-        assertThat(captor.getValue().getWakeReason())
-                .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
-    }
-
-    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
@@ -2913,26 +1989,6 @@
     }
 
     @Test
-    public void testFaceSensorProperties() throws RemoteException {
-        // GIVEN no face sensor properties
-        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
-
-        // THEN face is not possible
-        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
-                mSelectedUserInteractor.getSelectedUserId())).isFalse();
-
-        // WHEN there are face sensor properties
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-
-        // THEN face is possible but face does NOT start listening immediately
-        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
-                mSelectedUserInteractor.getSelectedUserId())).isTrue();
-        verifyFaceAuthenticateNeverCalled();
-        verifyFaceDetectNeverCalled();
-    }
-
-    @Test
     public void testFingerprintListeningStateWhenOccluded() {
         when(mAuthController.isUdfpsSupported()).thenReturn(true);
 
@@ -3014,123 +2070,11 @@
         authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE);
         mTestableLooper.processAllMessages();
         verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
-    }
 
-    @Test
-    public void onDisplayOn_nothingHappens() throws RemoteException {
-        // GIVEN
-        keyguardIsVisible();
-        enableStopFaceAuthOnDisplayOff();
-
-        // WHEN the default display state changes to ON
-        triggerDefaultDisplayStateChangeToOn();
-
-        // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth,
-        // NOT the display on event
-        verifyFaceAuthenticateNeverCalled();
-        verifyFaceDetectNeverCalled();
-    }
-
-    @Test
-    public void onDisplayOff_stopFaceAuth() throws RemoteException {
-        enableStopFaceAuthOnDisplayOff();
-
-        // GIVEN device is listening for face
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        clearInvocations(callback);
+        mFaceAuthenticationListener.getValue().onAuthEnrollmentStateChanged(false);
         mTestableLooper.processAllMessages();
-        verifyFaceAuthenticateCall();
-
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        // WHEN the default display state changes to OFF
-        triggerDefaultDisplayStateChangeToOff();
-
-        // THEN face listening is stopped.
-        verify(faceCancel).cancel();
-        verify(callback).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void onDisplayOff_whileAsleep_doesNotStopFaceAuth() throws RemoteException {
-        enableStopFaceAuthOnDisplayOff();
-        when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_ASLEEP);
-
-        // GIVEN device is listening for face
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        verifyFaceAuthenticateCall();
-
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        // WHEN the default display state changes to OFF
-        triggerDefaultDisplayStateChangeToOff();
-
-        // THEN face listening is NOT stopped.
-        verify(faceCancel, never()).cancel();
-        verify(callback, never()).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
-    }
-
-    @Test
-    public void onDisplayOff_whileWaking_doesNotStopFaceAuth() throws RemoteException {
-        enableStopFaceAuthOnDisplayOff();
-        when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_WAKING);
-
-        // GIVEN device is listening for face
-        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        verifyFaceAuthenticateCall();
-
-        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
-        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        // WHEN the default display state changes to OFF
-        triggerDefaultDisplayStateChangeToOff();
-
-        // THEN face listening is NOT stopped.
-        verify(faceCancel, never()).cancel();
-        verify(callback, never()).onBiometricRunningStateChanged(
-                eq(false), eq(BiometricSourceType.FACE));
-    }
-
-    private void triggerDefaultDisplayStateChangeToOn() {
-        triggerDefaultDisplayStateChangeTo(true);
-    }
-
-    private void triggerDefaultDisplayStateChangeToOff() {
-        triggerDefaultDisplayStateChangeTo(false);
-    }
-
-    /**
-     * @param on true for Display.STATE_ON, else Display.STATE_OFF
-     */
-    private void triggerDefaultDisplayStateChangeTo(boolean on) {
-        DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class);
-        DisplayInfo displayInfoWithDisplayState = new DisplayInfo();
-        displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF;
-        when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId()))
-                .thenReturn(displayInfoWithDisplayState);
-        mDisplayTracker.setAllDisplays(new Display[]{
-                new Display(
-                        displayManagerGlobal,
-                        mDisplayTracker.getDefaultDisplayId(),
-                        displayInfoWithDisplayState,
-                        DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
-                )
-        });
-        mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId());
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
     }
 
     private void verifyFingerprintAuthenticateNeverCalled() {
@@ -3151,37 +2095,12 @@
         verify(mFingerprintManager).detectFingerprint(any(), any(), any());
     }
 
-    private void verifyFaceAuthenticateNeverCalled() {
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), any());
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
-    }
-
-    private void verifyFaceAuthenticateCall() {
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), any());
-    }
-
-    private void verifyFaceDetectNeverCalled() {
-        verify(mFaceManager, never()).detectFace(any(), any(), any());
-    }
-
-    private void verifyFaceDetectCall() {
-        verify(mFaceManager).detectFace(any(), any(), any());
-    }
-
     private void userDeviceLockDown() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
                 .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
     }
 
-    private void supportsFaceDetection() throws RemoteException {
-        final boolean isClass3 = !mFaceSensorProperties.isEmpty()
-                && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG;
-        mFaceSensorProperties =
-                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3));
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-    }
-
     private void lockscreenBypassIsAllowed() {
         mockCanBypassLockscreen(true);
     }
@@ -3204,38 +2123,20 @@
     }
 
     private void faceAuthLockOut() {
-        mKeyguardUpdateMonitor.mFaceAuthenticationCallback
-                .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+        when(mFaceAuthInteractor.isLockedOut()).thenReturn(true);
+        mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged(
+                new ErrorFaceAuthenticationStatus(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "",
+                        0L));
     }
 
     private void statusBarShadeIsNotLocked() {
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
     }
 
-    private void statusBarShadeIsLocked() {
-        mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
-    }
-
     private void keyguardIsVisible() {
         mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
     }
 
-    private void triggerAuthInterrupt() {
-        mKeyguardUpdateMonitor.onAuthInterruptDetected(true);
-    }
-
-    private void occludingAppRequestsFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
-    }
-
-    private void secureCameraLaunched() {
-        mKeyguardUpdateMonitor.onCameraLaunched();
-    }
-
-    private void userCurrentlySwitching() {
-        mKeyguardUpdateMonitor.setSwitchingUser(true);
-    }
-
     private void fingerprintErrorTemporaryLockOut() {
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
@@ -3245,100 +2146,25 @@
         mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED);
     }
 
-    private void deviceInPostureStateClosed() {
-        mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_CLOSED);
-    }
-
-    private void successfulFingerprintAuth() {
-        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
-                .onAuthenticationSucceeded(
-                        new FingerprintManager.AuthenticationResult(null,
-                                null,
-                                mCurrentUserId,
-                                true));
-    }
-
-    private void triggerSuccessfulFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-        verify(mFaceManager).authenticate(any(),
-                any(),
-                mAuthenticationCallbackCaptor.capture(),
-                any(),
-                any());
-        mAuthenticationCallbackCaptor.getValue()
-                .onAuthenticationSucceeded(
-                        new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false));
-    }
-
     private void currentUserIsSystem() {
         when(mUserManager.isSystemUser()).thenReturn(true);
     }
 
-    private void biometricsNotDisabledThroughDevicePolicyManager() {
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
-                mSelectedUserInteractor.getSelectedUserId())).thenReturn(0);
-    }
-
     private void biometricsEnabledForCurrentUser() throws RemoteException {
         mBiometricEnabledOnKeyguardCallback.onChanged(true,
                 mSelectedUserInteractor.getSelectedUserId());
     }
 
-    private void biometricsDisabledForCurrentUser() throws RemoteException {
-        mBiometricEnabledOnKeyguardCallback.onChanged(
-                false,
-                mSelectedUserInteractor.getSelectedUserId()
-        );
-    }
-
-    private void primaryAuthRequiredEncrypted() {
-        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
-                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-    }
-
-    private void primaryAuthRequiredForWeakBiometricOnly() {
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
-    }
-
     private void primaryAuthNotRequiredByStrongAuthTracker() {
         when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(0);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
     }
 
-    private void currentUserDoesNotHaveTrust() {
-        mKeyguardUpdateMonitor.onTrustChanged(
-                false,
-                false,
-                mSelectedUserInteractor.getSelectedUserId(),
-                -1,
-                new ArrayList<>()
-        );
-    }
-
-    private void userNotCurrentlySwitching() {
-        mKeyguardUpdateMonitor.setSwitchingUser(false);
-    }
-
     private void keyguardNotGoingAway() {
         mKeyguardUpdateMonitor.setKeyguardGoingAway(false);
     }
 
-    private void bouncerFullyVisibleAndNotGoingToSleep() {
-        bouncerFullyVisible();
-        deviceNotGoingToSleep();
-    }
-
-    private void deviceNotGoingToSleep() {
-        mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
-    }
-
-    private void deviceGoingToSleep() {
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
-    }
-
     private void deviceIsInteractive() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
     }
@@ -3347,20 +2173,11 @@
         setKeyguardBouncerVisibility(true);
     }
 
-    private void bouncerNotVisible() {
-        setKeyguardBouncerVisibility(false);
-    }
-
     private void setKeyguardBouncerVisibility(boolean isVisible) {
         mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
     }
 
-    private void givenUdfpsSupported() {
-        when(mAuthController.isUdfpsSupported()).thenReturn(true);
-        Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
-    }
-
     private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
         BroadcastReceiver.PendingResult pendingResult =
                 new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
@@ -3386,31 +2203,6 @@
         mTestableLooper.processAllMessages();
     }
 
-    private void givenDetectFace() throws RemoteException {
-        // GIVEN bypass is enabled, face detection is supported and primary auth is required
-        lockscreenBypassIsAllowed();
-        supportsFaceDetection();
-        primaryAuthRequiredEncrypted();
-        keyguardIsVisible();
-        // fingerprint is NOT running, UDFPS is NOT supported
-
-        // WHEN the device wakes up
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-    }
-
-    private void enableStopFaceAuthOnDisplayOff() throws RemoteException {
-        cleanupKeyguardUpdateMonitor();
-        clearInvocations(mFaceManager);
-        clearInvocations(mFingerprintManager);
-        clearInvocations(mBiometricManager);
-        clearInvocations(mStatusBarStateController);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-        setupBiometrics(mKeyguardUpdateMonitor);
-        when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
-        assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
-    }
-
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
@@ -3486,11 +2278,10 @@
                     mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
                     mTrustManager, mSubscriptionManager, mUserManager,
                     mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
-                    mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
+                    mPackageManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider),
-                    mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker,
-                    mWakefulness, mSelectedUserInteractor);
+                    mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d2f45ae..3d7d701 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
-import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 
@@ -151,7 +150,6 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
 
         mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
         mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
         mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index ea7cc3d..6c91c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -100,7 +100,7 @@
 
     @Test
     fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() {
-        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false)
         whenever(authController.isShowing).thenReturn(true)
 
         assertThat(underTest.shouldShowFaceScanningAnim()).isFalse()
@@ -108,7 +108,7 @@
 
     @Test
     fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() {
-        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
         whenever(authController.isShowing).thenReturn(true)
 
         assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
@@ -116,7 +116,7 @@
 
     @Test
     fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() {
-        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
         whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
 
         assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f4122d5..ea20d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -338,6 +338,13 @@
         waitForIdleSync()
 
         assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+
+        // Check credential view persists after new attachment
+        container.onAttachedToWindow()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 11c5d3b..602f3dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -475,6 +475,22 @@
     }
 
     @Test
+    public void testOnAuthenticationFailedInvoked_whenBiometricReEnrollRequired() {
+        showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+        final int modality = BiometricAuthenticator.TYPE_FACE;
+        mAuthController.onBiometricError(modality,
+                BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL,
+                0 /* vendorCode */);
+
+        verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(),
+                mMessageCaptor.capture());
+
+        assertThat(mModalityCaptor.getValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(mContext.getString(
+                R.string.face_recalibrate_notification_content));
+    }
+
+    @Test
     public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
         testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 00ea78f..993dbac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -65,10 +65,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, false)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
             )
 
     private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
index ec17794..86b9b84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
@@ -21,13 +21,10 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.FaceAuthApiRequestReason
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -44,7 +41,6 @@
 @TestableLooper.RunWithLooper
 class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
 
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var hostView: View
     @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
     private lateinit var underTest: FaceAuthAccessibilityDelegate
@@ -55,14 +51,13 @@
         underTest =
             FaceAuthAccessibilityDelegate(
                 context.resources,
-                keyguardUpdateMonitor,
                 faceAuthInteractor,
             )
     }
 
     @Test
     fun shouldListenForFaceTrue_onInitializeAccessibilityNodeInfo_clickActionAdded() {
-        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
 
         // WHEN node is initialized
         val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
@@ -81,7 +76,7 @@
 
     @Test
     fun shouldListenForFaceFalse_onInitializeAccessibilityNodeInfo_clickActionNotAdded() {
-        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false)
 
         // WHEN node is initialized
         val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
@@ -94,7 +89,7 @@
 
     @Test
     fun performAccessibilityAction_actionClick_retriesFaceAuth() {
-        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
 
         // WHEN click action is performed
         underTest.performAccessibilityAction(
@@ -103,9 +98,6 @@
             null
         )
 
-        // THEN retry face auth
-        verify(keyguardUpdateMonitor)
-            .requestFaceAuth(eq(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION))
         verify(faceAuthInteractor).onAccessibilityAction()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index b4b02a2..a1b801c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -151,9 +152,11 @@
                 mock(StatusBarStateController::class.java),
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
+                FakeFingerprintPropertyRepository(),
                 FakeBiometricSettingsRepository(),
                 FakeSystemClock(),
                 mock(KeyguardUpdateMonitor::class.java),
+                testScope.backgroundScope,
             )
         displayStateInteractor =
             DisplayStateInteractorImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 5f0d4d4..f5b6f14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -36,13 +36,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -107,7 +107,6 @@
     @Mock private lateinit var udfpsView: UdfpsView
     @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
-    @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@@ -123,47 +122,52 @@
     @Before
     fun setup() {
         whenever(inflater.inflate(R.layout.udfps_view, null, false))
-            .thenReturn(udfpsView)
+                .thenReturn(udfpsView)
         whenever(inflater.inflate(R.layout.udfps_bp_view, null))
-            .thenReturn(mock(UdfpsBpView::class.java))
+                .thenReturn(mock(UdfpsBpView::class.java))
         whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
-            .thenReturn(mUdfpsKeyguardViewLegacy)
+                .thenReturn(mUdfpsKeyguardViewLegacy)
         whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null))
-            .thenReturn(mock(UdfpsFpmEmptyView::class.java))
+                .thenReturn(mock(UdfpsFpmEmptyView::class.java))
     }
 
     private fun withReason(
-        @ShowReason reason: Int,
-        isDebuggable: Boolean = false,
-        block: () -> Unit
+            @ShowReason reason: Int,
+            isDebuggable: Boolean = false,
+            enableDeviceEntryUdfpsRefactor: Boolean = false,
+            block: () -> Unit,
     ) {
+        if (enableDeviceEntryUdfpsRefactor) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+        }
         controllerOverlay = UdfpsControllerOverlay(
-            context,
-            inflater,
-            windowManager,
-            accessibilityManager,
-            statusBarStateController,
-            statusBarKeyguardViewManager,
-            keyguardUpdateMonitor,
-            dialogManager,
-            dumpManager,
-            transitionController,
-            configurationController,
-            keyguardStateController,
-            unlockedScreenOffAnimationController,
-            udfpsDisplayMode,
-            REQUEST_ID,
-            reason,
-            controllerCallback,
-            onTouch,
-            activityLaunchAnimator,
-            featureFlags,
-            primaryBouncerInteractor,
-            alternateBouncerInteractor,
-            isDebuggable,
-            udfpsKeyguardAccessibilityDelegate,
-            keyguardTransitionInteractor,
-            mSelectedUserInteractor,
+                context,
+                inflater,
+                windowManager,
+                accessibilityManager,
+                statusBarStateController,
+                statusBarKeyguardViewManager,
+                keyguardUpdateMonitor,
+                dialogManager,
+                dumpManager,
+                transitionController,
+                configurationController,
+                keyguardStateController,
+                unlockedScreenOffAnimationController,
+                udfpsDisplayMode,
+                REQUEST_ID,
+                reason,
+                controllerCallback,
+                onTouch,
+                activityLaunchAnimator,
+                primaryBouncerInteractor,
+                alternateBouncerInteractor,
+                isDebuggable,
+                udfpsKeyguardAccessibilityDelegate,
+                keyguardTransitionInteractor,
+                mSelectedUserInteractor,
         )
         block()
     }
@@ -185,12 +189,12 @@
         val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
         val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
         overlayParams = UdfpsOverlayParams(
-            sensorBounds,
-            overlayBounds,
-            DISPLAY_WIDTH,
-            DISPLAY_HEIGHT,
-            scaleFactor = 1f,
-            rotation
+                sensorBounds,
+                overlayBounds,
+                DISPLAY_WIDTH,
+                DISPLAY_HEIGHT,
+                scaleFactor = 1f,
+                rotation
         )
         block()
     }
@@ -200,8 +204,8 @@
         withReason(REASON_AUTH_BP) {
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
-                eq(controllerOverlay.overlayView),
-                layoutParamsCaptor.capture()
+                    eq(controllerOverlay.getTouchOverlay()),
+                    layoutParamsCaptor.capture()
             )
 
             // ROTATION_0 is the native orientation. Sensor should stay in the top left corner.
@@ -218,8 +222,8 @@
         withReason(REASON_AUTH_BP) {
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
-                eq(controllerOverlay.overlayView),
-                layoutParamsCaptor.capture()
+                    eq(controllerOverlay.getTouchOverlay()),
+                    layoutParamsCaptor.capture()
             )
 
             // ROTATION_180 is not supported. Sensor should stay in the top left corner.
@@ -236,8 +240,8 @@
         withReason(REASON_AUTH_BP) {
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
-                eq(controllerOverlay.overlayView),
-                layoutParamsCaptor.capture()
+                    eq(controllerOverlay.getTouchOverlay()),
+                    layoutParamsCaptor.capture()
             )
 
             // Sensor should be in the bottom left corner in ROTATION_90.
@@ -254,8 +258,8 @@
         withReason(REASON_AUTH_BP) {
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
-                eq(controllerOverlay.overlayView),
-                layoutParamsCaptor.capture()
+                    eq(controllerOverlay.getTouchOverlay()),
+                    layoutParamsCaptor.capture()
             )
 
             // Sensor should be in the top right corner in ROTATION_270.
@@ -270,7 +274,7 @@
     private fun showUdfpsOverlay() {
         val didShow = controllerOverlay.show(udfpsController, overlayParams)
 
-        verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
+        verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any())
         verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode))
         verify(udfpsView).animationViewController = any()
         verify(udfpsView).addView(any())
@@ -278,7 +282,7 @@
         assertThat(didShow).isTrue()
         assertThat(controllerOverlay.isShowing).isTrue()
         assertThat(controllerOverlay.isHiding).isFalse()
-        assertThat(controllerOverlay.overlayView).isNotNull()
+        assertThat(controllerOverlay.getTouchOverlay()).isNotNull()
     }
 
     @Test
@@ -295,14 +299,14 @@
 
     private fun hideUdfpsOverlay() {
         val didShow = controllerOverlay.show(udfpsController, overlayParams)
-        val view = controllerOverlay.overlayView
+        val view = controllerOverlay.getTouchOverlay()
         val didHide = controllerOverlay.hide()
 
         verify(windowManager).removeView(eq(view))
 
         assertThat(didShow).isTrue()
         assertThat(didHide).isTrue()
-        assertThat(controllerOverlay.overlayView).isNull()
+        assertThat(controllerOverlay.getTouchOverlay()).isNull()
         assertThat(controllerOverlay.animationViewController).isNull()
         assertThat(controllerOverlay.isShowing).isFalse()
         assertThat(controllerOverlay.isHiding).isTrue()
@@ -348,8 +352,8 @@
 
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
-                eq(controllerOverlay.overlayView),
-                layoutParamsCaptor.capture()
+                    eq(controllerOverlay.getTouchOverlay()),
+                    layoutParamsCaptor.capture()
             )
 
             // Layout params should use sensor bounds
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index c8c400d..e2cab29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -73,6 +73,7 @@
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
@@ -286,6 +287,7 @@
         // Create a fake background executor.
         mBiometricExecutor = new FakeExecutor(new FakeSystemClock());
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
         initUdfpsController(mOpticalProps);
     }
 
@@ -304,7 +306,6 @@
                 mStatusBarKeyguardViewManager,
                 mDumpManager,
                 mKeyguardUpdateMonitor,
-                mFeatureFlags,
                 mFalsingManager,
                 mPowerManager,
                 mAccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index a49150f..79f0625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -33,6 +34,7 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -105,15 +107,18 @@
                 FakeTrustRepository(),
                 testScope.backgroundScope,
                 mSelectedUserInteractor,
+                mock(KeyguardFaceAuthInteractor::class.java),
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
                 mock(StatusBarStateController::class.java),
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
+                FakeFingerprintPropertyRepository(),
                 mock(BiometricSettingsRepository::class.java),
                 mock(SystemClock::class.java),
                 mKeyguardUpdateMonitor,
+                testScope.backgroundScope,
             )
         mKeyguardTransitionInteractor =
             KeyguardTransitionInteractorFactory.create(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index c825d2e..834179bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -38,6 +38,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -97,7 +98,8 @@
                 deviceStateManager,
                 displayManager,
                 handler,
-                fakeExecutor
+                fakeExecutor,
+                UnconfinedTestDispatcher(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 2d8adca..f0d26b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -19,11 +19,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -31,7 +33,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.TestScope
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -47,8 +49,7 @@
     private lateinit var underTest: AlternateBouncerInteractor
     private lateinit var bouncerRepository: KeyguardBouncerRepository
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var deviceEntryFingerprintAuthRepository:
-        FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var systemClock: SystemClock
@@ -61,19 +62,28 @@
         bouncerRepository =
             KeyguardBouncerRepositoryImpl(
                 FakeSystemClock(),
-                TestCoroutineScope(),
+                TestScope().backgroundScope,
                 bouncerLogger,
             )
         biometricSettingsRepository = FakeBiometricSettingsRepository()
-        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+
+        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+        initializeUnderTest()
+    }
+
+    private fun initializeUnderTest() {
+        // Set any feature flags before creating the alternateBouncerInteractor
         underTest =
             AlternateBouncerInteractor(
                 statusBarStateController,
                 keyguardStateController,
                 bouncerRepository,
+                fingerprintPropertyRepository,
                 biometricSettingsRepository,
                 systemClock,
                 keyguardUpdateMonitor,
+                TestScope().backgroundScope,
             )
     }
 
@@ -156,7 +166,18 @@
     }
 
     @Test
+    fun canShowAlternateBouncerForFingerprint_rearFps() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+        initializeUnderTest()
+        givenCanShowAlternateBouncer()
+        fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
     fun alternateBouncerUiAvailable_fromMultipleSources() {
+        initializeUnderTest()
         assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
 
         // GIVEN there are two different sources indicating the alternate bouncer is available
@@ -178,7 +199,12 @@
     }
 
     private fun givenCanShowAlternateBouncer() {
-        bouncerRepository.setAlternateBouncerUIAvailable(true)
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            fingerprintPropertyRepository.supportsUdfps()
+        } else {
+            bouncerRepository.setAlternateBouncerUIAvailable(true)
+        }
+
         biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
         biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
         whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 50d2fd2..1e80732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
@@ -37,28 +38,38 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
+    @Mock private lateinit var keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor
+
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val underTest =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
+
+    private lateinit var underTest: BouncerInteractor
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
         overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
         overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
         overrideResource(R.string.kg_wrong_pin, MESSAGE_WRONG_PIN)
         overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
         overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
+
+        underTest =
+            utils.bouncerInteractor(
+                authenticationInteractor = authenticationInteractor,
+                keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
+            )
     }
 
     @Test
@@ -325,6 +336,13 @@
             assertThat(utils.powerRepository.userTouchRegistered).isTrue()
         }
 
+    @Test
+    fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
+        testScope.runTest {
+            underTest.onIntentionalUserInput()
+            verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
+        }
+
     private fun assertTryAgainMessage(
         message: String?,
         time: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index b48bc1d2..094616f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
 import com.android.systemui.res.R.string.kg_trust_agent_disabled
@@ -62,6 +63,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -122,6 +124,7 @@
                 fakeTrustRepository,
                 testScope.backgroundScope,
                 mSelectedUserInteractor,
+                mock(KeyguardFaceAuthInteractor::class.java),
             )
         underTest =
             BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index d6aa9ac..37a093e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
-import android.hardware.biometrics.BiometricSourceType
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.TestableResources
@@ -35,6 +34,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -72,6 +72,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
     private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
@@ -103,6 +104,7 @@
                 trustRepository,
                 testScope.backgroundScope,
                 mSelectedUserInteractor,
+                faceAuthInteractor,
             )
         whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -423,10 +425,7 @@
         mainHandler.setMode(FakeHandler.Mode.QUEUEING)
 
         // GIVEN bouncer should be delayed due to face auth
-        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
-        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-            .thenReturn(true)
-        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true)
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
 
         // WHEN bouncer show is requested
         underTest.show(true)
@@ -444,15 +443,12 @@
     }
 
     @Test
-    fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() {
+    fun noDelayBouncer_faceAuthNotAllowed() {
         mainHandler.setMode(FakeHandler.Mode.QUEUEING)
 
         // GIVEN bouncer should not be delayed because device isn't in the right posture for
         // face auth
-        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
-        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-            .thenReturn(true)
-        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false)
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false)
 
         // WHEN bouncer show is requested
         underTest.show(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index d1b120e..bdf5041 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
@@ -53,6 +54,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     private lateinit var underTest: PrimaryBouncerInteractor
 
@@ -75,6 +77,7 @@
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
                 mSelectedUserInteractor,
+                faceAuthInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
new file mode 100644
index 0000000..395d712
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -0,0 +1,253 @@
+/*
+ * 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.bouncer.ui.helper
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.google.common.truth.Truth.assertThat
+import java.util.Locale
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BouncerSceneLayoutTest : SysuiTestCase() {
+
+    data object Phone :
+        Device(
+            name = "phone",
+            width = SizeClass.COMPACT,
+            height = SizeClass.EXPANDED,
+            naturallyHeld = Vertically,
+        )
+    data object Tablet :
+        Device(
+            name = "tablet",
+            width = SizeClass.EXPANDED,
+            height = SizeClass.MEDIUM,
+            naturallyHeld = Horizontally,
+        )
+    data object Folded :
+        Device(
+            name = "folded",
+            width = SizeClass.COMPACT,
+            height = SizeClass.MEDIUM,
+            naturallyHeld = Vertically,
+        )
+    data object Unfolded :
+        Device(
+            name = "unfolded",
+            width = SizeClass.EXPANDED,
+            height = SizeClass.MEDIUM,
+            naturallyHeld = Vertically,
+            widthWhenUnnaturallyHeld = SizeClass.MEDIUM,
+            heightWhenUnnaturallyHeld = SizeClass.MEDIUM,
+        )
+    data object TallerFolded :
+        Device(
+            name = "taller folded",
+            width = SizeClass.COMPACT,
+            height = SizeClass.EXPANDED,
+            naturallyHeld = Vertically,
+        )
+    data object TallerUnfolded :
+        Device(
+            name = "taller unfolded",
+            width = SizeClass.EXPANDED,
+            height = SizeClass.EXPANDED,
+            naturallyHeld = Vertically,
+        )
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun testCases() =
+            listOf(
+                    Phone to
+                        Expected(
+                            whenNaturallyHeld = STANDARD,
+                            whenUnnaturallyHeld = SPLIT,
+                        ),
+                    Tablet to
+                        Expected(
+                            whenNaturallyHeld = SIDE_BY_SIDE,
+                            whenUnnaturallyHeld = STACKED,
+                        ),
+                    Folded to
+                        Expected(
+                            whenNaturallyHeld = STANDARD,
+                            whenUnnaturallyHeld = SPLIT,
+                        ),
+                    Unfolded to
+                        Expected(
+                            whenNaturallyHeld = SIDE_BY_SIDE,
+                            whenUnnaturallyHeld = STANDARD,
+                        ),
+                    TallerFolded to
+                        Expected(
+                            whenNaturallyHeld = STANDARD,
+                            whenUnnaturallyHeld = SPLIT,
+                        ),
+                    TallerUnfolded to
+                        Expected(
+                            whenNaturallyHeld = SIDE_BY_SIDE,
+                            whenUnnaturallyHeld = SIDE_BY_SIDE,
+                        ),
+                )
+                .flatMap { (device, expected) ->
+                    buildList {
+                        // Holding the device in its natural orientation (vertical or horizontal):
+                        add(
+                            TestCase(
+                                device = device,
+                                held = device.naturallyHeld,
+                                expected = expected.layout(heldNaturally = true),
+                            )
+                        )
+
+                        if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+                            add(
+                                TestCase(
+                                    device = device,
+                                    held = device.naturallyHeld,
+                                    isSideBySideSupported = false,
+                                    expected = STANDARD,
+                                )
+                            )
+                        }
+
+                        // Holding the device the other way:
+                        add(
+                            TestCase(
+                                device = device,
+                                held = device.naturallyHeld.flip(),
+                                expected = expected.layout(heldNaturally = false),
+                            )
+                        )
+
+                        if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+                            add(
+                                TestCase(
+                                    device = device,
+                                    held = device.naturallyHeld.flip(),
+                                    isSideBySideSupported = false,
+                                    expected = STANDARD,
+                                )
+                            )
+                        }
+                    }
+                }
+    }
+
+    @Parameterized.Parameter @JvmField var testCase: TestCase? = null
+
+    @Test
+    fun calculateLayout() {
+        testCase?.let { nonNullTestCase ->
+            with(nonNullTestCase) {
+                assertThat(
+                        calculateLayoutInternal(
+                            width = device.width(whenHeld = held),
+                            height = device.height(whenHeld = held),
+                            isSideBySideSupported = isSideBySideSupported,
+                        )
+                    )
+                    .isEqualTo(expected)
+            }
+        }
+    }
+
+    data class TestCase(
+        val device: Device,
+        val held: Held,
+        val expected: BouncerSceneLayout,
+        val isSideBySideSupported: Boolean = true,
+    ) {
+        override fun toString(): String {
+            return buildString {
+                append(device.name)
+                append(" width: ${device.width(held).name.lowercase(Locale.US)}")
+                append(" height: ${device.height(held).name.lowercase(Locale.US)}")
+                append(" when held $held")
+                if (!isSideBySideSupported) {
+                    append(" (side-by-side not supported)")
+                }
+            }
+        }
+    }
+
+    data class Expected(
+        val whenNaturallyHeld: BouncerSceneLayout,
+        val whenUnnaturallyHeld: BouncerSceneLayout,
+    ) {
+        fun layout(heldNaturally: Boolean): BouncerSceneLayout {
+            return if (heldNaturally) {
+                whenNaturallyHeld
+            } else {
+                whenUnnaturallyHeld
+            }
+        }
+    }
+
+    sealed class Device(
+        val name: String,
+        private val width: SizeClass,
+        private val height: SizeClass,
+        val naturallyHeld: Held,
+        private val widthWhenUnnaturallyHeld: SizeClass = height,
+        private val heightWhenUnnaturallyHeld: SizeClass = width,
+    ) {
+        fun width(whenHeld: Held): SizeClass {
+            return if (isHeldNaturally(whenHeld)) {
+                width
+            } else {
+                widthWhenUnnaturallyHeld
+            }
+        }
+
+        fun height(whenHeld: Held): SizeClass {
+            return if (isHeldNaturally(whenHeld)) {
+                height
+            } else {
+                heightWhenUnnaturallyHeld
+            }
+        }
+
+        private fun isHeldNaturally(whenHeld: Held): Boolean {
+            return whenHeld == naturallyHeld
+        }
+    }
+
+    sealed class Held {
+        abstract fun flip(): Held
+    }
+    data object Vertically : Held() {
+        override fun flip(): Held {
+            return Horizontally
+        }
+    }
+    data object Horizontally : Held() {
+        override fun flip(): Held {
+            return Vertically
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 2cc8f0a..90e0c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
@@ -61,6 +62,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
 
     lateinit var bouncerInteractor: PrimaryBouncerInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
@@ -86,6 +88,7 @@
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
                 mSelectedUserInteractor,
+                faceAuthInteractor,
             )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 125fe68..862c39c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -322,7 +322,6 @@
                             xPx = 30f * coordinate.x + 15,
                             yPx = 30f * coordinate.y + 15,
                             containerSizePx = 90,
-                            verticalOffsetPx = 0f,
                         )
                     }
 
@@ -369,7 +368,6 @@
             xPx = dotSize * coordinate.x + 15f,
             yPx = dotSize * coordinate.y + 15f,
             containerSizePx = containerSize,
-            verticalOffsetPx = 0f,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
index 034b802..112cec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
@@ -30,6 +30,8 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -44,102 +46,112 @@
 
     private val configurationController: ConfigurationController = mock()
     private val layoutInflater = TestLayoutInflater()
+    private val backgroundDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(backgroundDispatcher)
 
     val underTest = ConfigurationState(configurationController, context, layoutInflater)
 
     @Test
-    fun reinflateAndBindLatest_inflatesWithoutEmission() = runTest {
-        var callbackCount = 0
-        backgroundScope.launch {
-            underTest.reinflateAndBindLatest<View>(
-                resource = 0,
-                root = null,
-                attachToRoot = false,
-            ) {
-                callbackCount++
-                null
+    fun reinflateAndBindLatest_inflatesWithoutEmission() =
+        testScope.runTest {
+            var callbackCount = 0
+            backgroundScope.launch {
+                underTest.reinflateAndBindLatest<View>(
+                    resource = 0,
+                    root = null,
+                    attachToRoot = false,
+                    backgroundDispatcher,
+                ) {
+                    callbackCount++
+                    null
+                }
             }
-        }
 
-        // Inflates without an emission
-        runCurrent()
-        assertThat(layoutInflater.inflationCount).isEqualTo(1)
-        assertThat(callbackCount).isEqualTo(1)
-    }
-
-    @Test
-    fun reinflateAndBindLatest_reinflatesOnThemeChanged() = runTest {
-        var callbackCount = 0
-        backgroundScope.launch {
-            underTest.reinflateAndBindLatest<View>(
-                resource = 0,
-                root = null,
-                attachToRoot = false,
-            ) {
-                callbackCount++
-                null
-            }
-        }
-        runCurrent()
-
-        val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-            verify(configurationController, atLeastOnce()).addCallback(capture())
-        }
-
-        listOf(1, 2, 3).forEach { count ->
-            assertThat(layoutInflater.inflationCount).isEqualTo(count)
-            assertThat(callbackCount).isEqualTo(count)
-            configListeners.forEach { it.onThemeChanged() }
+            // Inflates without an emission
             runCurrent()
+            assertThat(layoutInflater.inflationCount).isEqualTo(1)
+            assertThat(callbackCount).isEqualTo(1)
         }
-    }
 
     @Test
-    fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() = runTest {
-        var callbackCount = 0
-        backgroundScope.launch {
-            underTest.reinflateAndBindLatest<View>(
-                resource = 0,
-                root = null,
-                attachToRoot = false,
-            ) {
-                callbackCount++
-                null
+    fun reinflateAndBindLatest_reinflatesOnThemeChanged() =
+        testScope.runTest {
+            var callbackCount = 0
+            backgroundScope.launch {
+                underTest.reinflateAndBindLatest<View>(
+                    resource = 0,
+                    root = null,
+                    attachToRoot = false,
+                    backgroundDispatcher,
+                ) {
+                    callbackCount++
+                    null
+                }
             }
-        }
-        runCurrent()
-
-        val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-            verify(configurationController, atLeastOnce()).addCallback(capture())
-        }
-
-        listOf(1, 2, 3).forEach { count ->
-            assertThat(layoutInflater.inflationCount).isEqualTo(count)
-            assertThat(callbackCount).isEqualTo(count)
-            configListeners.forEach { it.onDensityOrFontScaleChanged() }
             runCurrent()
-        }
-    }
 
-    @Test
-    fun testReinflateAndBindLatest_disposesOnCancel() = runTest {
-        var callbackCount = 0
-        var disposed = false
-        val job = launch {
-            underTest.reinflateAndBindLatest<View>(
-                resource = 0,
-                root = null,
-                attachToRoot = false,
-            ) {
-                callbackCount++
-                DisposableHandle { disposed = true }
+            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+                verify(configurationController, atLeastOnce()).addCallback(capture())
+            }
+
+            listOf(1, 2, 3).forEach { count ->
+                assertThat(layoutInflater.inflationCount).isEqualTo(count)
+                assertThat(callbackCount).isEqualTo(count)
+                configListeners.forEach { it.onThemeChanged() }
+                runCurrent()
             }
         }
 
-        runCurrent()
-        job.cancelAndJoin()
-        assertThat(disposed).isTrue()
-    }
+    @Test
+    fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() =
+        testScope.runTest {
+            var callbackCount = 0
+            backgroundScope.launch {
+                underTest.reinflateAndBindLatest<View>(
+                    resource = 0,
+                    root = null,
+                    attachToRoot = false,
+                    backgroundDispatcher,
+                ) {
+                    callbackCount++
+                    null
+                }
+            }
+            runCurrent()
+
+            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+                verify(configurationController, atLeastOnce()).addCallback(capture())
+            }
+
+            listOf(1, 2, 3).forEach { count ->
+                assertThat(layoutInflater.inflationCount).isEqualTo(count)
+                assertThat(callbackCount).isEqualTo(count)
+                configListeners.forEach { it.onDensityOrFontScaleChanged() }
+                runCurrent()
+            }
+        }
+
+    @Test
+    fun testReinflateAndBindLatest_disposesOnCancel() =
+        testScope.runTest {
+            var callbackCount = 0
+            var disposed = false
+            val job = launch {
+                underTest.reinflateAndBindLatest<View>(
+                    resource = 0,
+                    root = null,
+                    attachToRoot = false,
+                    backgroundDispatcher,
+                ) {
+                    callbackCount++
+                    DisposableHandle { disposed = true }
+                }
+            }
+
+            runCurrent()
+            job.cancelAndJoin()
+            assertThat(disposed).isTrue()
+        }
 
     inner class TestLayoutInflater : LayoutInflater(context) {
 
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 14ec4d4..16b2ed6 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
@@ -124,6 +124,39 @@
             assertThat(widgets()).containsExactly(communalItemRankEntry2, communalWidgetItemEntry2)
         }
 
+    @Test
+    fun reorderWidget_emitsWidgetsInNewOrder() =
+        testScope.runTest {
+            val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
+            val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+            widgetsToAdd.forEach {
+                val (widgetId, provider, priority) = it
+                communalWidgetDao.addWidget(
+                    widgetId = widgetId,
+                    provider = provider,
+                    priority = priority,
+                )
+            }
+            assertThat(widgets())
+                .containsExactly(
+                    communalItemRankEntry1,
+                    communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2
+                )
+
+            val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
+            communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+            assertThat(widgets())
+                .containsExactly(
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry1,
+                    communalWidgetItemEntry1
+                )
+        }
+
     data class FakeWidgetMetadata(
         val widgetId: Int,
         val provider: ComponentName,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 28fae81..182712a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -202,6 +202,20 @@
         }
 
     @Test
+    fun reorderWidgets_queryDb() =
+        testScope.runTest {
+            userUnlocked(true)
+            val repository = initCommunalWidgetRepository()
+            runCurrent()
+
+            val ids = listOf(104, 103, 101)
+            repository.updateWidgetOrder(ids)
+            runCurrent()
+
+            verify(communalWidgetDao).updateWidgetOrder(ids)
+        }
+
+    @Test
     fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
         testScope.runTest {
             communalEnabled(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e0567a4..16cfa23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.app.smartspace.SmartspaceTarget
-import android.provider.Settings
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -99,24 +98,6 @@
         }
 
     @Test
-    fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
-        testScope.runTest {
-            // Keyguard showing, and tutorial not started.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(
-                Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
-            )
-
-            val communalContent by collectLastValue(underTest.communalContent)
-
-            assertThat(communalContent!!).isNotEmpty()
-            communalContent!!.forEach { model ->
-                assertThat(model is CommunalContentModel.Tutorial).isTrue()
-            }
-        }
-
-    @Test
     fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -145,12 +126,11 @@
                 )
             widgetRepository.setCommunalWidgets(widgets)
 
-            val communalContent by collectLastValue(underTest.communalContent)
+            val widgetContent by collectLastValue(underTest.widgetContent)
 
-            assertThat(communalContent!!).isNotEmpty()
-            communalContent!!.forEachIndexed { index, model ->
-                assertThat((model as CommunalContentModel.Widget).appWidgetId)
-                    .isEqualTo(widgets[index].appWidgetId)
+            assertThat(widgetContent!!).isNotEmpty()
+            widgetContent!!.forEachIndexed { index, model ->
+                assertThat(model.appWidgetId).isEqualTo(widgets[index].appWidgetId)
             }
         }
 
@@ -183,48 +163,9 @@
             val targets = listOf(target1, target2, target3)
             smartspaceRepository.setLockscreenSmartspaceTargets(targets)
 
-            val communalContent by collectLastValue(underTest.communalContent)
-            assertThat(communalContent?.size).isEqualTo(1)
-            assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target3")
-        }
-
-    @Test
-    fun smartspace_smartspaceAndWidgetsAvailable_showSmartspaceAndWidgetContent() =
-        testScope.runTest {
-            // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            // Widgets available.
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = mock(),
-                    ),
-                    CommunalWidgetContentModel(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = mock(),
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
-
-            // Smartspace available.
-            val target = mock(SmartspaceTarget::class.java)
-            whenever(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
-
-            val communalContent by collectLastValue(underTest.communalContent)
-
-            assertThat(communalContent?.size).isEqualTo(3)
-            assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target")
-            assertThat(communalContent?.get(1)?.key).isEqualTo("widget_0")
-            assertThat(communalContent?.get(2)?.key).isEqualTo("widget_1")
+            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+            assertThat(smartspaceContent?.size).isEqualTo(1)
+            assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
         }
 
     @Test
@@ -236,55 +177,11 @@
             // Media is playing.
             mediaRepository.mediaPlaying.value = true
 
-            val communalContent by collectLastValue(underTest.communalContent)
+            val umoContent by collectLastValue(underTest.umoContent)
 
-            assertThat(communalContent?.size).isEqualTo(1)
-            assertThat(communalContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
-            assertThat(communalContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
-        }
-
-    @Test
-    fun ordering_smartspaceBeforeUmoBeforeWidgets() =
-        testScope.runTest {
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            // Widgets available.
-            val widgets =
-                listOf(
-                    CommunalWidgetContentModel(
-                        appWidgetId = 0,
-                        priority = 30,
-                        providerInfo = mock(),
-                    ),
-                    CommunalWidgetContentModel(
-                        appWidgetId = 1,
-                        priority = 20,
-                        providerInfo = mock(),
-                    ),
-                )
-            widgetRepository.setCommunalWidgets(widgets)
-
-            // Smartspace available.
-            val target = mock(SmartspaceTarget::class.java)
-            whenever(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
-
-            // Media playing.
-            mediaRepository.mediaPlaying.value = true
-
-            val communalContent by collectLastValue(underTest.communalContent)
-
-            // Order is smart space, then UMO, then widget content.
-            assertThat(communalContent?.size).isEqualTo(4)
-            assertThat(communalContent?.get(0))
-                .isInstanceOf(CommunalContentModel.Smartspace::class.java)
-            assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
-            assertThat(communalContent?.get(2))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
-            assertThat(communalContent?.get(3))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(umoContent?.size).isEqualTo(1)
+            assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
+            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 0004f52..910097e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
@@ -56,6 +57,13 @@
         )
 
     @Test
+    fun canSwipeToEnter_startsNull() =
+        testScope.runTest {
+            val values by collectValues(underTest.canSwipeToEnter)
+            assertThat(values[0]).isNull()
+        }
+
+    @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 6c990e45..40c9432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -30,7 +30,6 @@
 import android.view.SurfaceControlViewHost
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -51,6 +50,7 @@
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
@@ -173,7 +173,6 @@
             FakeFeatureFlags().apply {
                 set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
                 set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
-                set(Flags.FACE_AUTH_REFACTOR, true)
             }
         underTest.interactor =
             KeyguardQuickAffordanceInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b16c352..d246f0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -910,6 +910,20 @@
         assertATMSAndKeyguardViewMediatorStatesMatch();
     }
 
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testStartKeyguardExitAnimation_whenNotInteractive_doesShowAndUpdatesWM() {
+        // If the exit animation was triggered but the device became non-interactive, make sure
+        // relock happens
+        when(mPowerManager.isInteractive()).thenReturn(false);
+
+        startMockKeyguardExitAnimation();
+        cancelMockKeyguardExitAnimation();
+
+        verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
+        assertATMSAndKeyguardViewMediatorStatesMatch();
+    }
+
     /**
      * Interactions with the ActivityTaskManagerService and others are posted to an executor that
      * doesn't use the testable looper. Use this method to ensure those are run as well.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index f0ff77e..852f9a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -52,7 +52,6 @@
         MockitoAnnotations.initMocks(this)
         featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
         featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
-        featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
         powerInteractor = PowerInteractorFactory.create().powerInteractor
         keyguardRepository.setDozeAmount(0f)
         keyguardRepository.setKeyguardGoingAway(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 8d9bc75..45aca17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -57,7 +58,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
@@ -187,11 +187,7 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(FACE_AUTH_REFACTOR, true)
-                set(KEYGUARD_WM_STATE_REFACTOR, false)
-            }
+        featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) }
 
         powerRepository = FakePowerRepository()
         powerInteractor =
@@ -223,11 +219,13 @@
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 bouncerRepository = bouncerRepository,
+                fingerprintPropertyRepository = FakeFingerprintPropertyRepository(),
                 biometricSettingsRepository = biometricSettingsRepository,
                 systemClock = mock(SystemClock::class.java),
                 keyguardStateController = FakeKeyguardStateController(),
                 statusBarStateController = mock(StatusBarStateController::class.java),
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
+                scope = testScope.backgroundScope,
             )
 
         displayRepository = FakeDisplayRepository()
@@ -787,21 +785,19 @@
         }
 
     @Test
-    fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
+    fun everythingEmitsADefaultValueAndDoesNotErrorOut() =
         testScope.runTest {
-            featureFlags.set(FACE_AUTH_REFACTOR, false)
-
             underTest = createDeviceEntryFaceAuthRepositoryImpl()
             initCollectors()
 
             // Collecting any flows exposed in the public API doesn't throw any error
-            authStatus()
-            detectStatus()
-            authRunning()
-            bypassEnabled()
-            lockedOut()
-            canFaceAuthRun()
-            authenticated()
+            assertThat(authStatus()).isNull()
+            assertThat(detectStatus()).isNull()
+            assertThat(authRunning()).isNotNull()
+            assertThat(bypassEnabled()).isNotNull()
+            assertThat(lockedOut()).isNotNull()
+            assertThat(canFaceAuthRun()).isNotNull()
+            assertThat(authenticated()).isNotNull()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index a58bc52..2b7221e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -34,6 +34,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -69,6 +70,7 @@
                 authController,
                 keyguardUpdateMonitor,
                 testScope.backgroundScope,
+                UnconfinedTestDispatcher(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
index 9be5558..ae6c5b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -26,6 +26,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -49,7 +50,11 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         testScope = TestScope()
-        underTest = DevicePostureRepositoryImpl(postureController = devicePostureController)
+        underTest =
+            DevicePostureRepositoryImpl(
+                postureController = devicePostureController,
+                UnconfinedTestDispatcher()
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index e45f56a..b3e8fed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FaceSensorInfo
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.LockoutMode
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -40,8 +41,6 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
@@ -108,8 +107,6 @@
         val scheduler = TestCoroutineScheduler()
         val dispatcher = StandardTestDispatcher(scheduler)
         testScope = TestScope(dispatcher)
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
         bouncerRepository = FakeKeyguardBouncerRepository()
         faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
@@ -134,31 +131,35 @@
                 testScope.backgroundScope,
                 dispatcher,
                 faceAuthRepository,
-                PrimaryBouncerInteractor(
-                    bouncerRepository,
-                    mock(BouncerView::class.java),
-                    mock(Handler::class.java),
-                    mock(KeyguardStateController::class.java),
-                    mock(KeyguardSecurityModel::class.java),
-                    mock(PrimaryBouncerCallbackInteractor::class.java),
-                    mock(FalsingCollector::class.java),
-                    mock(DismissCallbackRegistry::class.java),
-                    context,
-                    keyguardUpdateMonitor,
-                    FakeTrustRepository(),
-                    testScope.backgroundScope,
-                    mSelectedUserInteractor,
-                ),
+                {
+                    PrimaryBouncerInteractor(
+                        bouncerRepository,
+                        mock(BouncerView::class.java),
+                        mock(Handler::class.java),
+                        mock(KeyguardStateController::class.java),
+                        mock(KeyguardSecurityModel::class.java),
+                        mock(PrimaryBouncerCallbackInteractor::class.java),
+                        mock(FalsingCollector::class.java),
+                        mock(DismissCallbackRegistry::class.java),
+                        context,
+                        keyguardUpdateMonitor,
+                        FakeTrustRepository(),
+                        testScope.backgroundScope,
+                        mSelectedUserInteractor,
+                        underTest,
+                    )
+                },
                 AlternateBouncerInteractor(
                     mock(StatusBarStateController::class.java),
                     mock(KeyguardStateController::class.java),
                     bouncerRepository,
+                    FakeFingerprintPropertyRepository(),
                     fakeBiometricSettingsRepository,
                     FakeSystemClock(),
                     keyguardUpdateMonitor,
+                    testScope.backgroundScope,
                 ),
                 keyguardTransitionInteractor,
-                featureFlags,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
                 fakeDeviceEntryFingerprintAuthRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index ad2ec72..706f94e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
@@ -54,7 +52,6 @@
     private val repository = testUtils.keyguardRepository
     private val sceneInteractor = testUtils.sceneInteractor()
     private val commandQueue = FakeCommandQueue()
-    private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) }
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val configurationRepository = FakeConfigurationRepository()
     private val shadeRepository = FakeShadeRepository()
@@ -66,7 +63,6 @@
             repository = repository,
             commandQueue = commandQueue,
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            featureFlags = featureFlags,
             sceneContainerFlags = testUtils.sceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index fe474fa..66c8a22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -302,10 +301,7 @@
                 dumpManager = mock(),
                 userHandle = UserHandle.SYSTEM,
             )
-        val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FACE_AUTH_REFACTOR, true)
-            }
+        val featureFlags = FakeFeatureFlags()
         val testDispatcher = StandardTestDispatcher()
         testScope = TestScope(testDispatcher)
         underTest =
@@ -357,7 +353,7 @@
                 }
 
             underTest.onQuickAffordanceTriggered(
-                configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}",
+                configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key",
                 expandable = expandable,
                 slotId = "",
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 347d580..bc4bae0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.common.shared.model.ContentDescription
@@ -31,7 +30,6 @@
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -48,6 +46,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
@@ -102,9 +101,11 @@
         overrideResource(
             R.array.config_keyguardQuickAffordanceDefaults,
             arrayOf(
-                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                    ":" +
                     BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
-                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                    ":" +
                     BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
             )
         )
@@ -168,10 +169,7 @@
                 dumpManager = mock(),
                 userHandle = UserHandle.SYSTEM,
             )
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FACE_AUTH_REFACTOR, true)
-            }
+        featureFlags = FakeFeatureFlags()
 
         val withDeps =
             KeyguardInteractorFactory.create(
@@ -216,9 +214,8 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(
-                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
-            )
+            assertThat(visibleModel.configKey)
+                .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::$configKey")
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -243,9 +240,8 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(
-                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::${configKey}"
-            )
+            assertThat(visibleModel.configKey)
+                .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::$configKey")
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -364,9 +360,8 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(
-                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
-            )
+            assertThat(visibleModel.configKey)
+                .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::$configKey")
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bf23bf8..bf6d5c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -121,11 +121,7 @@
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FACE_AUTH_REFACTOR, true)
-                set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
-            }
+        featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
 
         keyguardInteractor = createKeyguardInteractor()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index f9362a7..1f245f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -103,7 +102,7 @@
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         configurationRepository = FakeConfigurationRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
+        featureFlags = FakeFeatureFlags()
         trustRepository = FakeTrustRepository()
         powerRepository = FakePowerRepository()
         powerInteractor =
@@ -147,15 +146,18 @@
                     keyguardUpdateMonitor,
                     trustRepository,
                     testScope.backgroundScope,
-                    mSelectedUserInteractor
+                    mSelectedUserInteractor,
+                    keyguardFaceAuthInteractor = mock(),
                 ),
                 AlternateBouncerInteractor(
                     statusBarStateController = mock(),
                     keyguardStateController = mock(),
                     bouncerRepository,
+                    FakeFingerprintPropertyRepository(),
                     biometricSettingsRepository,
                     FakeSystemClock(),
                     keyguardUpdateMonitor,
+                    scope = testScope.backgroundScope,
                 ),
                 testScope.backgroundScope,
                 mockedContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 16d072e..87eee1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -25,8 +25,6 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -64,7 +62,6 @@
     private lateinit var bouncerRepository: KeyguardBouncerRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var burnInInteractor: BurnInInteractor
     private lateinit var shadeRepository: FakeShadeRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
@@ -80,8 +77,7 @@
         MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         configRepository = FakeConfigurationRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
-        KeyguardInteractorFactory.create(featureFlags = featureFlags).let {
+        KeyguardInteractorFactory.create().let {
             keyguardInteractor = it.keyguardInteractor
             keyguardRepository = it.repository
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 76c2589..740fce9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -40,6 +40,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.util.mockito.whenever
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,7 +49,6 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -57,10 +57,9 @@
     private lateinit var underTest: DefaultKeyguardBlueprint
     private lateinit var rootView: KeyguardRootView
     @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
-    @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection
+    @Mock private lateinit var mDefaultDeviceEntrySection: DefaultDeviceEntrySection
     @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
-    @Mock
-    private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection>
+    @Mock private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection>
     @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
     @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
     @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
@@ -79,7 +78,7 @@
         underTest =
             DefaultKeyguardBlueprint(
                 defaultIndicationAreaSection,
-                mDefaultDeviceEntryIconSection,
+                mDefaultDeviceEntrySection,
                 defaultShortcutsSection,
                 defaultAmbientIndicationAreaSection,
                 defaultSettingsPopupMenuSection,
@@ -106,18 +105,25 @@
         val prevBlueprint = mock(KeyguardBlueprint::class.java)
         val someSection = mock(KeyguardSection::class.java)
         whenever(prevBlueprint.sections)
-            .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection))
+            .thenReturn(underTest.sections.minus(mDefaultDeviceEntrySection).plus(someSection))
         val constraintLayout = ConstraintLayout(context, null)
         underTest.replaceViews(prevBlueprint, constraintLayout)
-        underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach {
+        underTest.sections.minus(mDefaultDeviceEntrySection).forEach {
             verify(it, never())?.addViews(constraintLayout)
         }
 
-        verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout)
+        verify(mDefaultDeviceEntrySection).addViews(constraintLayout)
         verify(someSection).removeViews(constraintLayout)
     }
 
     @Test
+    fun deviceEntryIconIsOnTop() {
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.replaceViews(null, constraintLayout)
+        underTest.sections.forEach { verify(it)?.addViews(constraintLayout) }
+    }
+
+    @Test
     fun applyConstraints() {
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
similarity index 80%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index a010ea9..67fba42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -30,14 +30,19 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
 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.shade.NotificationPanelView
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,7 +55,7 @@
 @ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
 @SmallTest
-class DefaultDeviceEntryIconSectionTest : SysuiTestCase() {
+class DefaultDeviceEntrySectionTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
@@ -58,7 +63,7 @@
     private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var falsingManager: FalsingManager
-    private lateinit var underTest: DefaultDeviceEntryIconSection
+    private lateinit var underTest: DefaultDeviceEntrySection
 
     @Before
     fun setup() {
@@ -69,7 +74,7 @@
         featureFlags =
             FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
         underTest =
-            DefaultDeviceEntryIconSection(
+            DefaultDeviceEntrySection(
                 keyguardUpdateMonitor,
                 authController,
                 windowManager,
@@ -81,6 +86,11 @@
                 { mock(DeviceEntryForegroundViewModel::class.java) },
                 { mock(DeviceEntryBackgroundViewModel::class.java) },
                 { falsingManager },
+                { mock(AlternateBouncerViewModel::class.java) },
+                { mock(NotificationShadeWindowController::class.java) },
+                TestScope().backgroundScope,
+                { mock(SwipeUpAnywhereGestureHandler::class.java) },
+                { mock(TapGestureDetector::class.java) },
             )
     }
 
@@ -165,4 +175,21 @@
         assertThat(constraint.layout.topMargin).isEqualTo(5)
         assertThat(constraint.layout.startMargin).isEqualTo(4)
     }
+
+    @Test
+    fun deviceEntryIconViewIsAboveAlternateBouncerView() {
+        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isGreaterThan(0)
+        val deviceEntryIconView = constraintLayout.getViewById(R.id.device_entry_icon_view)
+        val alternateBouncerView = constraintLayout.getViewById(R.id.alternate_bouncer)
+        assertThat(deviceEntryIconView).isNotNull()
+        assertThat(alternateBouncerView).isNotNull()
+
+        // device entry icon is above the alternate bouncer
+        assertThat(constraintLayout.indexOfChild(deviceEntryIconView))
+            .isGreaterThan(constraintLayout.indexOfChild(alternateBouncerView))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 1768f8c..fc9f54ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -27,7 +27,6 @@
 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.plugins.FalsingManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -50,7 +49,6 @@
     private lateinit var testScope: TestScope
 
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
-    @Mock private lateinit var falsingManager: FalsingManager
 
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
     private lateinit var transitionInteractor: KeyguardTransitionInteractor
@@ -69,7 +67,6 @@
             AlternateBouncerViewModel(
                 statusBarKeyguardViewManager,
                 transitionInteractor,
-                falsingManager,
             )
     }
 
@@ -106,36 +103,6 @@
         }
 
     @Test
-    fun clickListenerUpdate() =
-        runTest(UnconfinedTestDispatcher()) {
-            val clickListener by collectLastValue(underTest.onClickListener)
-
-            // keyguard state => ALTERNATE_BOUNCER
-            transitionRepository.sendTransitionStep(
-                stepToAlternateBouncer(0f, TransitionState.STARTED)
-            )
-            assertThat(clickListener).isNull()
-            transitionRepository.sendTransitionStep(stepToAlternateBouncer(.3f))
-            assertThat(clickListener).isNull()
-            transitionRepository.sendTransitionStep(stepToAlternateBouncer(.6f))
-            assertThat(clickListener).isNull()
-            transitionRepository.sendTransitionStep(stepToAlternateBouncer(1f))
-            assertThat(clickListener).isNotNull()
-
-            // ALTERNATE_BOUNCER -> keyguard state
-            transitionRepository.sendTransitionStep(
-                stepFromAlternateBouncer(0f, TransitionState.STARTED)
-            )
-            assertThat(clickListener).isNotNull()
-            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.3f))
-            assertThat(clickListener).isNull()
-            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.6f))
-            assertThat(clickListener).isNull()
-            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(1f))
-            assertThat(clickListener).isNull()
-        }
-
-    @Test
     fun forcePluginOpen() =
         runTest(UnconfinedTestDispatcher()) {
             val forcePluginOpen by collectLastValue(underTest.forcePluginOpen)
@@ -156,6 +123,27 @@
             assertThat(forcePluginOpen).isFalse()
         }
 
+    @Test
+    fun registerForDismissGestures() =
+        runTest(UnconfinedTestDispatcher()) {
+            val registerForDismissGestures by collectLastValue(underTest.registerForDismissGestures)
+            transitionRepository.sendTransitionStep(
+                stepToAlternateBouncer(0f, TransitionState.STARTED)
+            )
+            transitionRepository.sendTransitionStep(stepToAlternateBouncer(.3f))
+            transitionRepository.sendTransitionStep(stepToAlternateBouncer(.6f))
+            transitionRepository.sendTransitionStep(stepToAlternateBouncer(1f))
+            assertThat(registerForDismissGestures).isTrue()
+
+            transitionRepository.sendTransitionStep(
+                stepFromAlternateBouncer(0f, TransitionState.STARTED)
+            )
+            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.3f))
+            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.6f))
+            transitionRepository.sendTransitionStep(stepFromAlternateBouncer(1f))
+            assertThat(registerForDismissGestures).isFalse()
+        }
+
     private fun stepToAlternateBouncer(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 32d28a3..1584be0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
@@ -52,6 +51,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
@@ -116,9 +116,11 @@
         overrideResource(
             R.array.config_keyguardQuickAffordanceDefaults,
             arrayOf(
-                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                    ":" +
                     BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
-                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                    ":" +
                     BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
             )
         )
@@ -138,7 +140,6 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.FACE_AUTH_REFACTOR, true)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 25d1419..67c4e26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -22,14 +22,13 @@
 import android.os.UserHandle
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dock.DockManagerFake
-import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
@@ -49,6 +48,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
@@ -129,7 +129,6 @@
 
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.FACE_AUTH_REFACTOR, true)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
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 d3019f1..e6d6cf2 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
@@ -22,6 +22,7 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
@@ -31,9 +32,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -108,9 +107,7 @@
 
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
 
-        val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
-
-        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val withDeps = KeyguardInteractorFactory.create()
         keyguardInteractor = withDeps.keyguardInteractor
         repository = withDeps.repository
         configurationRepository = withDeps.configurationRepository
@@ -134,7 +131,6 @@
                 deviceEntryInteractor =
                     mock { whenever(isBypassEnabled).thenReturn(MutableStateFlow(false)) },
                 dozeParameters = mock(),
-                featureFlags,
                 keyguardInteractor,
                 keyguardTransitionInteractor,
                 notificationsKeyguardInteractor =
@@ -346,11 +342,7 @@
         DaggerKeyguardRootViewModelTestWithFakes_TestComponent.factory()
             .create(
                 test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        setDefault(Flags.NEW_AOD_TRANSITION)
-                        set(Flags.FACE_AUTH_REFACTOR, true)
-                    },
+                featureFlags = FakeFeatureFlagsClassicModule(),
                 mocks =
                     TestMocksModule(
                         dozeParameters = dozeParams,
@@ -363,6 +355,11 @@
                 block()
             }
 
+    @Before
+    fun before() {
+        mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
+    }
+
     @Test
     fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() = runTest {
         val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 0b3bc9d..d07836d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -94,6 +94,8 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
+            keyguardRoot = utils.keyguardRootViewModel(),
+            notifications = utils.notificationsPlaceholderViewModel(),
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index c50be04..2314c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -94,10 +93,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(FACE_AUTH_REFACTOR, true)
-                        set(FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(FULL_SCREEN_USER_SWITCHER, true) },
                 mocks = TestMocksModule(),
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 26704da..ba72b4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -14,86 +14,56 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> {
-        val repository: FakeKeyguardTransitionRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val shadeRepository: FakeShadeRepository
 
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    private val testScope = kosmos.testScope
+    private val repository = kosmos.fakeKeyguardTransitionRepository
+    private val shadeRepository = kosmos.shadeRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest =
+        LockscreenToDreamingTransitionViewModel(
+            interactor = kosmos.keyguardTransitionInteractor,
+            shadeDependentFlows = kosmos.shadeDependentFlows,
+        )
 
-    private fun TestComponent.shadeExpanded(expanded: Boolean) {
-        if (expanded) {
-            shadeRepository.setQsExpansion(1f)
-        } else {
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
-        }
-    }
-
-    private val testComponent: TestComponent =
-        DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, true)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks = TestMocksModule(),
-            )
     @Test
     fun lockscreenFadeOut() =
-        testComponent.runTest {
+        testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
             repository.sendTransitionSteps(
                 steps =
@@ -116,7 +86,7 @@
 
     @Test
     fun lockscreenTranslationY() =
-        testComponent.runTest {
+        testScope.runTest {
             val pixels = 100
             val values by collectValues(underTest.lockscreenTranslationY(pixels))
 
@@ -141,7 +111,7 @@
 
     @Test
     fun deviceEntryParentViewAlpha_shadeExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val values by collectValues(underTest.deviceEntryParentViewAlpha)
             shadeExpanded(true)
             runCurrent()
@@ -165,7 +135,7 @@
 
     @Test
     fun deviceEntryParentViewAlpha_shadeNotExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
             shadeExpanded(false)
             runCurrent()
@@ -197,4 +167,14 @@
             ownerName = "LockscreenToDreamingTransitionViewModelTest"
         )
     }
+
+    private fun shadeExpanded(expanded: Boolean) {
+        if (expanded) {
+            shadeRepository.setQsExpansion(1f)
+        } else {
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLockscreenShadeExpansion(0f)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index ff3135a6..3536d5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -14,87 +14,56 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> {
-        val repository: FakeKeyguardTransitionRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val shadeRepository: FakeShadeRepository
 
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
-
-    private fun TestComponent.shadeExpanded(expanded: Boolean) {
-        if (expanded) {
-            shadeRepository.setQsExpansion(1f)
-        } else {
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
-        }
-    }
-
-    private val testComponent: TestComponent =
-        DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, true)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks = TestMocksModule(),
-            )
+    private val testScope = kosmos.testScope
+    private val repository = kosmos.fakeKeyguardTransitionRepository
+    private val shadeRepository = kosmos.shadeRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest =
+        LockscreenToOccludedTransitionViewModel(
+            interactor = kosmos.keyguardTransitionInteractor,
+            shadeDependentFlows = kosmos.shadeDependentFlows,
+        )
 
     @Test
     fun lockscreenFadeOut() =
-        testComponent.runTest {
+        testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
             repository.sendTransitionSteps(
                 steps =
@@ -116,7 +85,7 @@
 
     @Test
     fun lockscreenTranslationY() =
-        testComponent.runTest {
+        testScope.runTest {
             val pixels = 100
             val values by collectValues(underTest.lockscreenTranslationY(pixels))
             repository.sendTransitionSteps(
@@ -136,7 +105,7 @@
 
     @Test
     fun lockscreenTranslationYIsCanceled() =
-        testComponent.runTest {
+        testScope.runTest {
             val pixels = 100
             val values by collectValues(underTest.lockscreenTranslationY(pixels))
             repository.sendTransitionSteps(
@@ -158,7 +127,7 @@
 
     @Test
     fun deviceEntryParentViewAlpha_shadeExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val values by collectValues(underTest.deviceEntryParentViewAlpha)
             shadeExpanded(true)
             runCurrent()
@@ -179,7 +148,7 @@
 
     @Test
     fun deviceEntryParentViewAlpha_shadeNotExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
             shadeExpanded(false)
             runCurrent()
@@ -211,4 +180,14 @@
             ownerName = "LockscreenToOccludedTransitionViewModelTest"
         )
     }
+
+    private fun shadeExpanded(expanded: Boolean) {
+        if (expanded) {
+            shadeRepository.setQsExpansion(1f)
+        } else {
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLockscreenShadeExpansion(0f)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 8afd8e4..049e4e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -86,10 +86,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, true)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
                 mocks = TestMocksModule(),
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
index 5058b16..6512290 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -23,8 +23,6 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -54,7 +52,6 @@
     private lateinit var configRepository: FakeConfigurationRepository
     private lateinit var bouncerRepository: KeyguardBouncerRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var shadeRepository: FakeShadeRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
 
@@ -67,16 +64,12 @@
         overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
         testScope = TestScope()
         shadeRepository = FakeShadeRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
-        KeyguardInteractorFactory.create(
-                featureFlags = featureFlags,
-            )
-            .also {
-                keyguardInteractor = it.keyguardInteractor
-                keyguardRepository = it.repository
-                configRepository = it.configurationRepository
-                bouncerRepository = it.bouncerRepository
-            }
+        KeyguardInteractorFactory.create().also {
+            keyguardInteractor = it.keyguardInteractor
+            keyguardRepository = it.repository
+            configRepository = it.configurationRepository
+            bouncerRepository = it.bouncerRepository
+        }
         val udfpsKeyguardInteractor =
             UdfpsKeyguardInteractor(
                 configRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index f039f53..95b2fe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -59,7 +57,6 @@
     private lateinit var bouncerRepository: KeyguardBouncerRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
     private lateinit var shadeRepository: FakeShadeRepository
 
@@ -75,14 +72,12 @@
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         fakeCommandQueue = FakeCommandQueue()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
         bouncerRepository = FakeKeyguardBouncerRepository()
         transitionRepository = FakeKeyguardTransitionRepository()
         shadeRepository = FakeShadeRepository()
         val keyguardInteractor =
             KeyguardInteractorFactory.create(
                     repository = keyguardRepository,
-                    featureFlags = featureFlags,
                 )
                 .keyguardInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index c1805db..848a94b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -23,8 +23,6 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -75,7 +73,6 @@
     private lateinit var keyguardInteractor: KeyguardInteractor
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var shadeRepository: FakeShadeRepository
-    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
@@ -83,16 +80,12 @@
         testScope = TestScope()
         transitionRepository = FakeKeyguardTransitionRepository()
         shadeRepository = FakeShadeRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
-        KeyguardInteractorFactory.create(
-                featureFlags = featureFlags,
-            )
-            .also {
-                keyguardInteractor = it.keyguardInteractor
-                keyguardRepository = it.repository
-                configRepository = it.configurationRepository
-                bouncerRepository = it.bouncerRepository
-            }
+        KeyguardInteractorFactory.create().also {
+            keyguardInteractor = it.keyguardInteractor
+            keyguardRepository = it.repository
+            configRepository = it.configurationRepository
+            bouncerRepository = it.bouncerRepository
+        }
 
         val transitionInteractor =
             KeyguardTransitionInteractorFactory.create(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index f4293f0..50f0eb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -94,6 +94,7 @@
                 fakeHandler,
                 configurationController,
                 ResourcesSplitShadeStateController(),
+                mock<KeyguardMediaControllerLogger>(),
                 mock<DumpManager>()
             )
         keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
@@ -104,7 +105,7 @@
     fun testHiddenWhenHostIsHidden() {
         whenever(mediaHost.visible).thenReturn(false)
 
-        keyguardMediaController.refreshMediaPosition()
+        keyguardMediaController.refreshMediaPosition(TEST_REASON)
 
         assertThat(mediaContainerView.visibility).isEqualTo(GONE)
     }
@@ -118,7 +119,7 @@
 
     private fun testStateVisibility(state: Int, visibility: Int) {
         whenever(statusBarStateController.state).thenReturn(state)
-        keyguardMediaController.refreshMediaPosition()
+        keyguardMediaController.refreshMediaPosition(TEST_REASON)
         assertThat(mediaContainerView.visibility).isEqualTo(visibility)
     }
 
@@ -126,7 +127,7 @@
     fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() {
         settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0)
 
-        keyguardMediaController.refreshMediaPosition()
+        keyguardMediaController.refreshMediaPosition(TEST_REASON)
 
         assertThat(mediaContainerView.visibility).isEqualTo(GONE)
     }
@@ -135,7 +136,7 @@
     fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() {
         settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1)
 
-        keyguardMediaController.refreshMediaPosition()
+        keyguardMediaController.refreshMediaPosition(TEST_REASON)
 
         assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
     }
@@ -234,4 +235,8 @@
         whenever(statusBarStateController.isDozing).thenReturn(true)
         statusBarStateListener.onDozingChanged(true)
     }
+
+    private companion object {
+        private const val TEST_REASON = "test reason"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9dfb5a5..e082ca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -47,13 +47,13 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
@@ -305,7 +305,11 @@
 
         MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender,
                 MediaOutputController mediaOutputController) {
-            super(context, broadcastSender, mediaOutputController);
+            super(
+                    context,
+                    broadcastSender,
+                    mediaOutputController, /* includePlaybackAndAppMetadata */
+                    true);
 
             mAdapter = mMediaOutputBaseAdapter;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 379136b..d5dc502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -49,13 +49,13 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
@@ -394,8 +394,14 @@
 
     @NonNull
     private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
-        return new MediaOutputDialog(mContext, false, mBroadcastSender,
-                controller, mDialogLaunchAnimator, mUiEventLogger);
+        return new MediaOutputDialog(
+                mContext,
+                false,
+                mBroadcastSender,
+                controller,
+                mDialogLaunchAnimator,
+                mUiEventLogger,
+                true);
     }
 
     private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index cf43b2e..b7618d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -45,7 +45,6 @@
 import androidx.test.ext.truth.content.IntentSubject.assertThat
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
 import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
@@ -56,6 +55,7 @@
 import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -162,6 +162,7 @@
             noteTaskBubblesController =
                 FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)),
             applicationScope = testScope,
+            bgCoroutineContext = testScope.backgroundScope.coroutineContext
         )
 
     // region onBubbleExpandChanged
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
new file mode 100644
index 0000000..db9e548
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -0,0 +1,86 @@
+package com.android.systemui.qs
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.View
+import android.widget.Scroller
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PagedTileLayoutTest : SysuiTestCase() {
+
+    @Mock private lateinit var pageIndicator: PageIndicator
+    @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+
+    private lateinit var pageTileLayout: TestPagedTileLayout
+    private lateinit var scroller: Scroller
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        pageTileLayout = TestPagedTileLayout(mContext)
+        pageTileLayout.setPageIndicator(pageIndicator)
+        verify(pageIndicator).setOnKeyListener(captor.capture())
+        setViewWidth(pageTileLayout, width = PAGE_WIDTH)
+        scroller = pageTileLayout.mScroller
+    }
+
+    private fun setViewWidth(view: View, width: Int) {
+        view.left = 0
+        view.right = width
+    }
+
+    @Test
+    fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+        pageTileLayout.currentPageIndex = 0
+
+        sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+
+        assertThat(scroller.isFinished).isFalse() // aka we're scrolling
+        assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
+    }
+
+    @Test
+    fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+        pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
+
+        sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+
+        assertThat(scroller.isFinished).isFalse() // aka we're scrolling
+        assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
+    }
+
+    private fun sendUpEvent(keyCode: Int) {
+        val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
+        captor.value.onKey(pageIndicator, keyCode, event)
+    }
+
+    /**
+     * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this
+     * up otherwise would require setting adapter etc
+     */
+    class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) {
+
+        var currentPageIndex: Int = 0
+
+        override fun getCurrentItem(): Int {
+            return currentPageIndex
+        }
+    }
+
+    companion object {
+        const val PAGE_WIDTH = 200
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
index a9f8ea0..81d02b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
@@ -85,7 +85,7 @@
         `when`(sharedPreferences.edit()).thenReturn(editor)
 
         tile = Tile()
-        customTileStatePersister = CustomTileStatePersister(mockContext)
+        customTileStatePersister = CustomTileStatePersisterImpl(mockContext)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index 3808c7e..313ccb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -228,16 +228,16 @@
                 showPairNewDevice = true
             )
 
-            val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group)
-            val pairNewLayout =
-                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group)
+            val seeAllButton = bluetoothTileDialog.requireViewById<View>(R.id.see_all_button)
+            val pairNewButton =
+                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_button)
             val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
             val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
 
-            assertThat(seeAllLayout).isNotNull()
-            assertThat(seeAllLayout.visibility).isEqualTo(GONE)
-            assertThat(pairNewLayout).isNotNull()
-            assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE)
+            assertThat(seeAllButton).isNotNull()
+            assertThat(seeAllButton.visibility).isEqualTo(GONE)
+            assertThat(pairNewButton).isNotNull()
+            assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
             assertThat(adapter.itemCount).isEqualTo(1)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index fb5dd21..99993f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -113,9 +112,7 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), any())
-            assertThat(bluetoothTileDialogViewModel.dialog?.isShowing).isTrue()
             verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
         }
     }
@@ -125,7 +122,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
         }
     }
@@ -136,7 +132,6 @@
             backgroundExecutor.execute {
                 bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
 
-                assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
                 verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
             }
         }
@@ -147,7 +142,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(deviceItemInteractor).deviceItemUpdate
         }
     }
@@ -157,7 +151,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(bluetoothStateInteractor).bluetoothStateUpdate
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index 4c173cc..e236f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -220,45 +220,57 @@
 
     @Test
     fun testUpdateDeviceItemOnClick_connectedMedia_setActive() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).setActive()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).setActive()
+            verify(logger)
+                .logDeviceClick(
+                    cachedDevice1.address,
+                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
+                )
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_activeMedia_disconnect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).disconnect()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).disconnect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).disconnect()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).disconnect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_saved_connect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).connect()
-        verify(logger).logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).connect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+        }
     }
 
     private fun createFactory(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
new file mode 100644
index 0000000..937744d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.qs.tiles.impl.airplate.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AirplaneModeTileDataInteractorTest : SysuiTestCase() {
+
+    private val airplaneModeRepository = FakeAirplaneModeRepository()
+
+    private val underTest: AirplaneModeTileDataInteractor =
+        AirplaneModeTileDataInteractor(airplaneModeRepository)
+
+    @Test
+    fun alwaysAvailable() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).hasSize(1)
+        assertThat(availability.last()).isTrue()
+    }
+
+    @Test
+    fun dataMatchesTheRepository() = runTest {
+        val dataList: List<AirplaneModeTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+
+        airplaneModeRepository.setIsAirplaneMode(true)
+        runCurrent()
+
+        airplaneModeRepository.setIsAirplaneMode(false)
+        runCurrent()
+
+        assertThat(dataList).hasSize(3)
+        assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+    }
+
+    private companion object {
+
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..81bde81
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.qs.tiles.impl.airplate.domain.interactor
+
+import android.provider.Settings
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
+import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AirplaneModeTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val mobileConnectionsRepository = FakeMobileConnectionsRepository()
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val airplaneModeRepository = FakeAirplaneModeRepository()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        AirplaneModeTileUserActionInteractor(
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                connectivityRepository,
+                mobileConnectionsRepository,
+            ),
+            inputHandler
+        )
+
+    @Test
+    fun handleClickInEcmMode() = runTest {
+        val isInAirplaneMode = false
+        airplaneModeRepository.setIsAirplaneMode(isInAirplaneMode)
+        mobileConnectionsRepository.setIsInEcmState(true)
+
+        underTest.handleInput(click(AirplaneModeTileModel(isInAirplaneMode)))
+
+        assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action)
+                .isEqualTo(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)
+        }
+        assertThat(airplaneModeRepository.isAirplaneMode.value).isFalse()
+    }
+
+    @Test
+    fun handleClickNotInEcmMode() = runTest {
+        val isInAirplaneMode = false
+        airplaneModeRepository.setIsAirplaneMode(isInAirplaneMode)
+        mobileConnectionsRepository.setIsInEcmState(isInAirplaneMode)
+
+        underTest.handleInput(click(AirplaneModeTileModel(false)))
+
+        assertThat(inputHandler).handledNoInputs()
+        assertThat(airplaneModeRepository.isAirplaneMode.value).isTrue()
+    }
+
+    @Test
+    fun handleLongClick() = runTest {
+        underTest.handleInput(longClick(AirplaneModeTileModel(false)))
+
+        assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_AIRPLANE_MODE_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
new file mode 100644
index 0000000..cf076c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.qs.tiles.impl.custom.data.repository
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileRepositoryTest : SysuiTestCase() {
+
+    private val testScope = TestScope()
+
+    private val persister = FakeCustomTileStatePersister()
+
+    private val underTest: CustomTileRepository =
+        CustomTileRepositoryImpl(
+            TileSpec.create(TEST_COMPONENT),
+            persister,
+            testScope.testScheduler,
+        )
+
+    @Test
+    fun persistableTileIsRestoredForUser() =
+        testScope.runTest {
+            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+            persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun notPersistableTileIsNotRestored() =
+        testScope.runTest {
+            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun emptyPersistedStateIsHandled() =
+        testScope.runTest {
+            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun updatingWithPersistableTilePersists() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingWithNotPersistableTileDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updateWithTileEmits() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingPeristableWithDefaultsPersists() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingNotPersistableWithDefaultsDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updatingPeristableWithErrorDefaultsDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updateWithDefaultsEmits() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun getTileForAnotherUserReturnsNull() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isNull()
+        }
+
+    @Test
+    fun getTilesForAnotherUserEmpty() =
+        testScope.runTest {
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun updatingWithTileForTheSameUserAddsData() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
+            runCurrent()
+
+            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+        }
+
+    @Test
+    fun updatingWithTileForAnotherUserOverridesTile() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+            underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+        }
+
+    @Test
+    fun updatingWithDefaultsForTheSameUserAddsData() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
+            runCurrent()
+
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+        }
+
+    @Test
+    fun updatingWithDefaultsForAnotherUserOverridesTile() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+            underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+        }
+
+    private companion object {
+
+        val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+
+        val TEST_USER_1 = UserHandle.of(1)!!
+        val TEST_TILE_1 =
+            Tile().apply {
+                label = "test_tile_1"
+                icon = Icon.createWithContentUri("file://test_1")
+            }
+        val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier)
+        val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+
+        val TEST_USER_2 = UserHandle.of(2)!!
+        val TEST_TILE_2 =
+            Tile().apply {
+                label = "test_tile_2"
+                icon = Icon.createWithContentUri("file://test_2")
+            }
+        val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier)
+        val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
new file mode 100644
index 0000000..eebb145
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.text.format.DateUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var tileServiceManager: TileServiceManager
+
+    private val testScope = TestScope()
+
+    private val defaultsRepository = FakeCustomTileDefaultsRepository()
+    private val customTileStatePersister = FakeCustomTileStatePersister()
+    private val customTileRepository =
+        FakeCustomTileRepository(
+            TEST_TILE_SPEC,
+            customTileStatePersister,
+            testScope.testScheduler,
+        )
+
+    private lateinit var underTest: CustomTileInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            CustomTileInteractor(
+                TEST_USER,
+                defaultsRepository,
+                customTileRepository,
+                tileServiceManager,
+                testScope.backgroundScope,
+                testScope.testScheduler,
+            )
+    }
+
+    @Test
+    fun activeTileIsAvailableAfterRestored() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(true)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+
+            underTest.init()
+
+            assertThat(underTest.tile).isEqualTo(TEST_TILE)
+            assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+        }
+
+    @Test
+    fun notActiveTileIsAvailableAfterUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+            val initJob = launch { underTest.init() }
+
+            underTest.updateTile(TEST_TILE)
+            runCurrent()
+            initJob.join()
+
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+        }
+
+    @Test
+    fun notActiveTileIsAvailableAfterDefaultsUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+            val initJob = launch { underTest.init() }
+
+            defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+            defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+            runCurrent()
+            initJob.join()
+
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+
+    @Test
+    fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(true)
+            val tiles = collectValues(underTest.tiles)
+
+            val initJob = backgroundScope.launch { underTest.init() }
+            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+
+            // Is still suspended
+            assertThat(initJob.isActive).isTrue()
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun initSuspendedForNotActiveTileWithoutUpdates() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+
+            val initJob = backgroundScope.launch { underTest.init() }
+            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+
+            // Is still suspended
+            assertThat(initJob.isActive).isTrue()
+            assertThat(tiles()).isEmpty()
+        }
+
+    private companion object {
+
+        val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+        val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
+        val TEST_USER = UserHandle.of(1)!!
+        val TEST_TILE =
+            Tile().apply {
+                label = "test_tile_1"
+                icon = Icon.createWithContentUri("file://test_1")
+            }
+        val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index ea8acc7..22fb152 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.android.settingslib.RestrictedLockUtils
 import com.android.systemui.SysuiTestCase
@@ -45,8 +46,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -54,14 +53,15 @@
 
 /** Tests all possible [QSTileUserAction]s. If you need */
 @MediumTest
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class QSTileViewModelUserInputTest : SysuiTestCase() {
 
     @Mock private lateinit var qsTileLogger: QSTileLogger
     @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
 
-    @Parameter lateinit var userAction: QSTileUserAction
+    // TODO(b/299909989): this should be parametrised. b/299096521 blocks this.
+    private val userAction: QSTileUserAction = QSTileUserAction.Click(null)
 
     private val tileConfig =
         QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
@@ -180,15 +180,4 @@
             testCoroutineDispatcher,
             scope.backgroundScope,
         )
-
-    companion object {
-
-        @JvmStatic
-        @Parameterized.Parameters
-        fun data(): Iterable<QSTileUserAction> =
-            listOf(
-                QSTileUserAction.Click(null),
-                QSTileUserAction.LongClick(null),
-            )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index ef129c8..42e27ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -19,7 +19,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -39,14 +38,11 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
@@ -90,47 +86,15 @@
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
-        val authenticationInteractor = utils.authenticationInteractor()
-
         underTest =
             QuickSettingsSceneViewModel(
-                deviceEntryInteractor =
-                    utils.deviceEntryInteractor(
-                        authenticationInteractor = authenticationInteractor,
-                        sceneInteractor = sceneInteractor,
-                    ),
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
+                notifications = utils.notificationsPlaceholderViewModel(),
             )
     }
 
     @Test
-    fun onContentClicked_deviceUnlocked_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
-            runCurrent()
-
-            underTest.onContentClicked()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
-            runCurrent()
-
-            underTest.onContentClicked()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-        }
-
-    @Test
     fun destinationsNotCustomizing() =
         testScope.runTest {
             val destinations by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 6a054cd..c953743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -61,7 +61,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -152,6 +151,8 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
+            keyguardRoot = utils.keyguardRootViewModel(),
+            notifications = utils.notificationsPlaceholderViewModel(),
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -237,6 +238,7 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
+                notifications = utils.notificationsPlaceholderViewModel(),
             )
 
         utils.deviceEntryRepository.setUnlocked(false)
@@ -270,6 +272,9 @@
     }
 
     @Test
+    fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) }
+
+    @Test
     fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
         testScope.runTest {
             emulateUserDrivenTransition(SceneKey.Bouncer)
@@ -333,7 +338,7 @@
         testScope.runTest {
             val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
-            assertTrue(deviceEntryInteractor.canSwipeToEnter.value)
+            assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
             assertCurrentScene(SceneKey.Lockscreen)
 
             // Emulate a user swipe to dismiss the lockscreen.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index c4ec56c..adc1c61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -145,6 +145,18 @@
         }
 
     @Test
+    fun startsInLockscreenScene() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState()
+
+            underTest.start()
+            runCurrent()
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
     fun switchToLockscreenWhenDeviceLocks() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 5969bd8..0173c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -74,10 +75,15 @@
             .forEach { flagToken ->
                 setFlagsRule.enableFlags(flagToken)
                 aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
+                overrideResource(
+                    R.bool.config_sceneContainerFrameworkEnabled,
+                    testCase.isResourceConfigEnabled
+                )
             }
 
         underTest =
             SceneContainerFlagsImpl(
+                context = context,
                 featureFlagsClassic = featureFlags,
                 isComposeAvailable = testCase.isComposeAvailable,
             )
@@ -91,13 +97,12 @@
     internal data class TestCase(
         val isComposeAvailable: Boolean,
         val areAllFlagsSet: Boolean,
+        val isResourceConfigEnabled: Boolean,
         val expectedEnabled: Boolean,
     ) {
         override fun toString(): String {
-            return """
-                (compose=$isComposeAvailable + flags=$areAllFlagsSet) -> expected=$expectedEnabled
-            """
-                .trimIndent()
+            return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
+                " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
         }
     }
 
@@ -105,17 +110,20 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun testCases() = buildList {
-            repeat(4) { combination ->
-                val isComposeAvailable = combination and 0b10 != 0
-                val areAllFlagsSet = combination and 0b01 != 0
+            repeat(8) { combination ->
+                val isComposeAvailable = combination and 0b100 != 0
+                val areAllFlagsSet = combination and 0b010 != 0
+                val isResourceConfigEnabled = combination and 0b001 != 0
 
-                val expectedEnabled = isComposeAvailable && areAllFlagsSet
+                val expectedEnabled =
+                    isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
 
                 add(
                     TestCase(
                         isComposeAvailable = isComposeAvailable,
                         areAllFlagsSet = areAllFlagsSet,
                         expectedEnabled = expectedEnabled,
+                        isResourceConfigEnabled = isResourceConfigEnabled,
                     )
                 )
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index ba8a666..03878b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -147,6 +147,7 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -335,6 +336,7 @@
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+    @Mock private ActiveNotificationsInteractor mActiveNotificationsInteractor;
     @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
     @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
 
@@ -709,6 +711,7 @@
                 mKeyguardInteractor,
                 mActivityStarter,
                 mSharedNotificationContainerInteractor,
+                mActiveNotificationsInteractor,
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
@@ -783,6 +786,7 @@
                 mKeyguardFaceAuthInteractor,
                 mShadeRepository,
                 mShadeInteractor,
+                mActiveNotificationsInteractor,
                 mJavaAdapter,
                 mCastController,
                 new ResourcesSplitShadeStateController()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 722fb2c..36b4435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -31,7 +31,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -56,7 +55,6 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
@@ -1075,33 +1073,6 @@
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
         verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
-        verify(mUpdateMonitor).requestFaceAuth(
-                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
-    }
-
-    @Test
-    public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
-        StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.getStatusBarStateListener();
-        statusBarStateListener.onStateChanged(KEYGUARD);
-        mNotificationPanelViewController.setDozing(true, false);
-
-        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
-        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
-        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
-
-        verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
-    }
-
-    @Test
-    public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
-        StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.getStatusBarStateListener();
-        statusBarStateListener.onStateChanged(SHADE_LOCKED);
-
-        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
-
-        verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 8403ac5..39b306b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -188,7 +188,6 @@
                 keyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                featureFlags,
                 sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index d89491c..0587633 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -65,6 +65,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -97,6 +98,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
@@ -111,9 +113,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -265,6 +266,7 @@
                             FakeTrustRepository(),
                             testScope.backgroundScope,
                             mSelectedUserInteractor,
+                            mock(KeyguardFaceAuthInteractor::class.java)
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9c8816c..29b1366 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -251,6 +251,7 @@
                             FakeTrustRepository(),
                             testScope.backgroundScope,
                             mSelectedUserInteractor,
+                            mock(),
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 26b84e3..1dbb297 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -80,6 +80,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
@@ -178,6 +180,8 @@
     protected SysuiStatusBarStateController mStatusBarStateController;
     protected ShadeInteractor mShadeInteractor;
 
+    protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
+
     protected Handler mMainHandler;
     protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
 
@@ -220,7 +224,6 @@
                 mKeyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                featureFlags,
                 sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
@@ -290,6 +293,9 @@
                 )
         );
 
+        mActiveNotificationsInteractor =
+                new ActiveNotificationsInteractor(new ActiveNotificationListRepository());
+
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
 
@@ -362,6 +368,7 @@
                 mock(KeyguardFaceAuthInteractor.class),
                 mShadeRepository,
                 mShadeInteractor,
+                mActiveNotificationsInteractor,
                 new JavaAdapter(mTestScope.getBackgroundScope()),
                 mCastController,
                 splitShadeStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 61e4370..65e0fa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -105,10 +105,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, false)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
                 mocks =
                     TestMocksModule(
                         dozeParameters = dozeParameters,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
index 92eb6ed..6e6e438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -86,10 +86,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, false)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
                 mocks =
                     TestMocksModule(
                         dozeParameters = dozeParameters,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 729f3f8..565e20a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -91,10 +91,7 @@
             .create(
                 test = this,
                 featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FACE_AUTH_REFACTOR, false)
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
                 mocks =
                     TestMocksModule(
                         dozeParameters = dozeParameters,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 0d3b2b3..e2640af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -101,6 +101,7 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
+                notifications = utils.notificationsPlaceholderViewModel(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
index ea7c068..ffde601 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.data.repository.FakeShadeRepository
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,6 +62,7 @@
                 falsingCollector,
                 dragDownloadCallback,
                 naturalScrollingSettingObserver,
+                FakeShadeRepository(),
                 mContext,
         ).also {
             it.expandCallback = expandCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
index 63e46d1..459040a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -18,7 +18,6 @@
 
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 
-import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
@@ -56,7 +55,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
@@ -72,6 +70,7 @@
 import com.android.systemui.keyguard.util.IndicationHelper;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -249,7 +248,6 @@
         mFlags = new FakeFeatureFlags();
         mFlags.set(KEYGUARD_TALKBACK_FIX, true);
         mFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
-        mFlags.set(FACE_AUTH_REFACTOR, false);
         mController = new KeyguardIndicationController(
                 mContext,
                 mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index dd3ac92..aa53558 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -479,7 +479,7 @@
         createController();
 
         // GIVEN face has already unlocked the device
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithFace(anyInt())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()).thenReturn(true);
 
         String message = "A message";
         mController.setVisible(true);
@@ -586,7 +586,7 @@
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
         when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false);
 
         mController.setVisible(true);
@@ -602,7 +602,7 @@
         String message = mContext.getString(R.string.keyguard_retry);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
         when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
 
         mController.setVisible(true);
         mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8fa7cd2..7546dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -66,8 +66,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -93,88 +93,98 @@
         whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
 
         uiEventLogger = UiEventLoggerFake()
-        controller = object : StatusBarStateControllerImpl(
-            uiEventLogger,
-            interactionJankMonitor,
-            mock(),
-            { shadeInteractor }
-        ) {
-            override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator }
-        }
+        controller =
+            object :
+                StatusBarStateControllerImpl(
+                    uiEventLogger,
+                    interactionJankMonitor,
+                    mock(),
+                    { shadeInteractor }
+                ) {
+                override fun createDarkAnimator(): ObjectAnimator {
+                    return mockDarkAnimator
+                }
+            }
 
-        val powerInteractor = PowerInteractor(
-            FakePowerRepository(),
-            FalsingCollectorFake(),
-            mock(),
-            controller)
+        val powerInteractor =
+            PowerInteractor(FakePowerRepository(), FalsingCollectorFake(), mock(), controller)
         val keyguardRepository = FakeKeyguardRepository()
         val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val featureFlags = FakeFeatureFlagsClassic()
         val shadeRepository = FakeShadeRepository()
         val sceneContainerFlags = FakeSceneContainerFlags()
         val configurationRepository = FakeConfigurationRepository()
-        val keyguardInteractor = KeyguardInteractor(
-            keyguardRepository,
-            FakeCommandQueue(),
-            powerInteractor,
-            featureFlags,
-            sceneContainerFlags,
-            FakeKeyguardBouncerRepository(),
-            configurationRepository,
-            shadeRepository,
-            utils::sceneInteractor)
-        val keyguardTransitionInteractor = KeyguardTransitionInteractor(
-            testScope.backgroundScope,
-            keyguardTransitionRepository,
-            { keyguardInteractor },
-            { fromLockscreenTransitionInteractor },
-            { fromPrimaryBouncerTransitionInteractor })
-        fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor(
-            keyguardTransitionRepository,
-            keyguardTransitionInteractor,
-            testScope.backgroundScope,
-            keyguardInteractor,
-            featureFlags,
-            shadeRepository,
-            powerInteractor,
-            {
-                InWindowLauncherUnlockAnimationInteractor(
-                    InWindowLauncherUnlockAnimationRepository(),
-                    testScope,
-                    keyguardTransitionInteractor,
-                    { FakeKeyguardSurfaceBehindRepository() },
-                    mock(),
-                )
-            })
-        fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
-            keyguardTransitionRepository,
-            keyguardTransitionInteractor,
-            testScope.backgroundScope,
-            keyguardInteractor,
-            featureFlags,
-            mock(),
-            mock(),
-            powerInteractor)
-        shadeInteractor = ShadeInteractorImpl(
-            testScope.backgroundScope,
-            FakeDeviceProvisioningRepository(),
-            FakeDisableFlagsRepository(),
-            mock(),
-            keyguardRepository,
-            keyguardTransitionInteractor,
-            powerInteractor,
-            FakeUserSetupRepository(),
-            mock(),
-            ShadeInteractorLegacyImpl(
-                testScope.backgroundScope,
+        val keyguardInteractor =
+            KeyguardInteractor(
                 keyguardRepository,
-                SharedNotificationContainerInteractor(
-                    configurationRepository,
-                    mContext,
-                    ResourcesSplitShadeStateController()),
+                FakeCommandQueue(),
+                powerInteractor,
+                sceneContainerFlags,
+                FakeKeyguardBouncerRepository(),
+                configurationRepository,
                 shadeRepository,
+                utils::sceneInteractor
             )
-        )
+        val keyguardTransitionInteractor =
+            KeyguardTransitionInteractor(
+                testScope.backgroundScope,
+                keyguardTransitionRepository,
+                { keyguardInteractor },
+                { fromLockscreenTransitionInteractor },
+                { fromPrimaryBouncerTransitionInteractor }
+            )
+        fromLockscreenTransitionInteractor =
+            FromLockscreenTransitionInteractor(
+                keyguardTransitionRepository,
+                keyguardTransitionInteractor,
+                testScope.backgroundScope,
+                keyguardInteractor,
+                featureFlags,
+                shadeRepository,
+                powerInteractor,
+                {
+                    InWindowLauncherUnlockAnimationInteractor(
+                        InWindowLauncherUnlockAnimationRepository(),
+                        testScope,
+                        keyguardTransitionInteractor,
+                        { FakeKeyguardSurfaceBehindRepository() },
+                        mock(),
+                    )
+                }
+            )
+        fromPrimaryBouncerTransitionInteractor =
+            FromPrimaryBouncerTransitionInteractor(
+                keyguardTransitionRepository,
+                keyguardTransitionInteractor,
+                testScope.backgroundScope,
+                keyguardInteractor,
+                featureFlags,
+                mock(),
+                mock(),
+                powerInteractor
+            )
+        shadeInteractor =
+            ShadeInteractorImpl(
+                testScope.backgroundScope,
+                FakeDeviceProvisioningRepository(),
+                FakeDisableFlagsRepository(),
+                mock(),
+                keyguardRepository,
+                keyguardTransitionInteractor,
+                powerInteractor,
+                FakeUserSetupRepository(),
+                mock(),
+                ShadeInteractorLegacyImpl(
+                    testScope.backgroundScope,
+                    keyguardRepository,
+                    SharedNotificationContainerInteractor(
+                        configurationRepository,
+                        mContext,
+                        ResourcesSplitShadeStateController()
+                    ),
+                    shadeRepository,
+                )
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index d1a46fc..057dcb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -62,7 +62,7 @@
     private val ongoingCallRepository = OngoingCallRepository()
 
     private val underTest =
-        StatusBarModeRepositoryImpl(
+        StatusBarModePerDisplayRepositoryImpl(
                 testScope.backgroundScope,
                 DISPLAY_ID,
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 428574b..fa5fad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
@@ -57,6 +58,7 @@
     @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
     @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
     @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
+    @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
 
@@ -75,6 +77,7 @@
                 groupExpansionManagerImpl,
                 notificationIconAreaController,
                 renderListInteractor,
+                activeNotificationsInteractor,
             )
         coordinator.attach(pipeline)
         afterRenderListListener = withArgCaptor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
new file mode 100644
index 0000000..4ab3cd4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class ActiveNotificationsInteractorTest : SysuiTestCase() {
+
+    @Component(modules = [SysUITestModule::class])
+    @SysUISingleton
+    interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> {
+        val activeNotificationListRepository: ActiveNotificationListRepository
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase): TestComponent
+        }
+    }
+
+    private val testComponent: TestComponent =
+        DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
+
+    @Test
+    fun testAreAnyNotificationsPresent_isTrue() =
+        testComponent.runTest {
+            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+            activeNotificationListRepository.setActiveNotifs(2)
+            runCurrent()
+
+            assertThat(areAnyNotificationsPresent).isTrue()
+            assertThat(underTest.areAnyNotificationsPresentValue).isTrue()
+        }
+
+    @Test
+    fun testAreAnyNotificationsPresent_isFalse() =
+        testComponent.runTest {
+            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+            activeNotificationListRepository.setActiveNotifs(0)
+            runCurrent()
+
+            assertThat(areAnyNotificationsPresent).isFalse()
+            assertThat(underTest.areAnyNotificationsPresentValue).isFalse()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasClearableSilentNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasNoClearableNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index a64ac67..22c5bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -114,9 +114,46 @@
     }
 
     @Test
+    public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
+        int resId = R.string.clear_all_notifications_text;
+        mView.setClearAllButtonText(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+                .getText().toString()).contains("Clear all");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setClearAllButtonText(resId);
+        mView.setClearAllButtonText(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
+        int resId = R.string.accessibility_clear_all;
+        mView.setClearAllButtonDescription(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+                .getContentDescription().toString()).contains("Clear all notifications");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setClearAllButtonDescription(resId);
+        mView.setClearAllButtonDescription(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
     public void testSetMessageString_resourceOnlyFetchedOnce() {
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
-        verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+        int resId = R.string.unlock_to_see_notif_text;
+        mView.setMessageString(resId);
+        verify(mSpyContext).getString(eq(resId));
 
         clearInvocations(mSpyContext);
 
@@ -124,22 +161,23 @@
                 .getText().toString()).contains("Unlock");
 
         // Set it a few more times, it shouldn't lead to the resource being fetched again
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
+        mView.setMessageString(resId);
+        mView.setMessageString(resId);
 
         verify(mSpyContext, never()).getString(anyInt());
     }
 
     @Test
     public void testSetMessageIcon_resourceOnlyFetchedOnce() {
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
-        verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+        int resId = R.drawable.ic_friction_lock_closed;
+        mView.setMessageIcon(resId);
+        verify(mSpyContext).getDrawable(eq(resId));
 
         clearInvocations(mSpyContext);
 
         // Set it a few more times, it shouldn't lead to the resource being fetched again
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+        mView.setMessageIcon(resId);
+        mView.setMessageIcon(resId);
 
         verify(mSpyContext, never()).getDrawable(anyInt());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 57a7c3c..0ba820f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -18,37 +18,222 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class FooterViewModelTest : SysuiTestCase() {
-    private val repository = ActiveNotificationListRepository()
-    private val interactor = SeenNotificationsInteractor(repository)
-    private val underTest = FooterViewModel(interactor)
+    private lateinit var footerViewModel: FooterViewModel
 
-    @Test
-    fun testMessageVisible_whenFilteredNotifications() = runTest {
-        val message by collectLastValue(underTest.message)
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                ActivatableNotificationViewModelModule::class,
+                FooterViewModelModule::class,
+                HeadlessSystemUserModeModule::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val configurationRepository: FakeConfigurationRepository
+        val keyguardRepository: FakeKeyguardRepository
+        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+        val shadeRepository: FakeShadeRepository
+        val powerRepository: FakePowerRepository
 
-        repository.hasFilteredOutSeenNotifications.value = true
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                featureFlags: FakeFeatureFlagsClassicModule,
+                mocks: TestMocksModule,
+            ): TestComponent
+        }
+    }
 
-        assertThat(message?.visible).isTrue()
+    private val dozeParameters: DozeParameters = mock()
+
+    private val testComponent: TestComponent =
+        DaggerFooterViewModelTest_TestComponent.factory()
+            .create(
+                test = this,
+                featureFlags =
+                    FakeFeatureFlagsClassicModule {
+                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
+                    },
+                mocks =
+                    TestMocksModule(
+                        dozeParameters = dozeParameters,
+                    )
+            )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
+
+        // The underTest in the component is Optional, because that matches the provider we
+        // currently have for the footer view model.
+        footerViewModel = testComponent.underTest.get()
     }
 
     @Test
-    fun testMessageVisible_whenNoFilteredNotifications() = runTest {
-        val message by collectLastValue(underTest.message)
+    fun testMessageVisible_whenFilteredNotifications() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.message.isVisible)
 
-        repository.hasFilteredOutSeenNotifications.value = false
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
 
-        assertThat(message?.visible).isFalse()
-    }
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun testMessageVisible_whenNoFilteredNotifications() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.message.isVisible)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun testClearAllButtonVisible_whenHasClearableNotifs() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(visible?.value).isTrue()
+        }
+
+    @Test
+    fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(visible?.value).isFalse()
+        }
+
+    @Test
+    fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+            runCurrent()
+
+            // WHEN shade is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setLegacyShadeExpansion(1f)
+            // AND QS not expanded
+            shadeRepository.setQsExpansion(0f)
+            // AND device is awake
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            runCurrent()
+
+            // AND there are clearable notifications
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            // THEN button visibility should animate
+            assertThat(visible?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun testClearAllButtonAnimating_whenShadeNotExpanded() =
+        testComponent.runTest {
+            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+            runCurrent()
+
+            // WHEN shade is collapsed
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setLegacyShadeExpansion(0f)
+            // AND QS not expanded
+            shadeRepository.setQsExpansion(0f)
+            // AND device is awake
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            runCurrent()
+
+            // AND there are clearable notifications
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            // THEN button visibility should not animate
+            assertThat(visible?.isAnimating).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 0341035..360a373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -26,10 +26,10 @@
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.runTest
 import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.shared.activeNotificationModel
 import com.android.systemui.statusbar.notification.shared.byIsAmbient
 import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
 import com.android.systemui.statusbar.notification.shared.byIsPulsing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 68761ef..b3fc25c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
@@ -92,9 +93,7 @@
                 test = this,
                 featureFlags =
                     FakeFeatureFlagsClassicModule {
-                        setDefault(Flags.FACE_AUTH_REFACTOR)
                         set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
-                        setDefault(Flags.NEW_AOD_TRANSITION)
                     },
                 mocks =
                     TestMocksModule(
@@ -115,6 +114,7 @@
                 lastSleepReason = WakeSleepReason.OTHER,
             )
         }
+        mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index c2a1519..7415645 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -43,10 +43,10 @@
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
-import com.android.systemui.statusbar.notification.shared.activeNotificationModel
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
 import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
@@ -105,7 +105,6 @@
                 test = this,
                 featureFlags =
                     FakeFeatureFlagsClassicModule {
-                        setDefault(Flags.FACE_AUTH_REFACTOR)
                         set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
                     },
                 mocks =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 3e331a6..0a9bac9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -999,11 +999,25 @@
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
     }
 
+    @Test
+    public void shouldNotBubbleUp_suspended() {
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createSuspendedBubble()))
+                .isFalse();
+    }
+
+    private NotificationEntry createSuspendedBubble() {
+        return createBubble(null, null, true);
+    }
+
     private NotificationEntry createBubble() {
-        return createBubble(null, null);
+        return createBubble(null, null, false);
     }
 
     private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
+        return createBubble(groupKey, groupAlert, false);
+    }
+
+    private NotificationEntry createBubble(String groupKey, Integer groupAlert, Boolean suspended) {
         Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
                 PendingIntent.getActivity(mContext, 0,
                         new Intent().setPackage(mContext.getPackageName()),
@@ -1031,6 +1045,7 @@
                 .setNotification(n)
                 .setImportance(IMPORTANCE_HIGH)
                 .setCanBubble(true)
+                .setSuspended(suspended)
                 .build();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index acc5cea..7361f6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -35,6 +35,10 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
+    init {
+        mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    }
+
     override val provider by lazy {
         NotificationInterruptStateProviderWrapper(
             NotificationInterruptStateProviderImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 9e7df5f..d2c046c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -28,6 +28,10 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
+    init {
+        mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    }
+
     override val provider by lazy {
         VisualInterruptionDecisionProviderImpl(
             ambientDisplayConfiguration,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 5dcb6c9..2ac0cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -595,6 +595,13 @@
     }
 
     @Test
+    fun testShouldNotBubble_bubbleAppSuspended() {
+        ensureBubbleState()
+        assertShouldNotBubble(buildBubbleEntry { packageSuspended = true })
+        assertNoEventsLogged()
+    }
+
+    @Test
     fun testShouldNotFsi_noFullScreenIntent() {
         forEachFsiState {
             assertShouldNotFsi(buildFsiEntry { hasFsi = false })
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
new file mode 100644
index 0000000..0356c2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.notification.interruption
+
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.EventLog
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+
+object VisualInterruptionDecisionProviderTestUtil {
+    fun createProviderByFlag(
+        ambientDisplayConfiguration: AmbientDisplayConfiguration,
+        batteryController: BatteryController,
+        deviceProvisionedController: DeviceProvisionedController,
+        eventLog: EventLog,
+        flags: NotifPipelineFlags,
+        globalSettings: GlobalSettings,
+        headsUpManager: HeadsUpManager,
+        keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+        keyguardStateController: KeyguardStateController,
+        @Main mainHandler: Handler,
+        newLogger: VisualInterruptionDecisionLogger,
+        oldLogger: NotificationInterruptLogger,
+        powerManager: PowerManager,
+        statusBarStateController: StatusBarStateController,
+        systemClock: SystemClock,
+        uiEventLogger: UiEventLogger,
+        userTracker: UserTracker
+    ): VisualInterruptionDecisionProvider {
+        return if (VisualInterruptionRefactor.isEnabled) {
+            VisualInterruptionDecisionProviderImpl(
+                ambientDisplayConfiguration,
+                batteryController,
+                deviceProvisionedController,
+                eventLog,
+                globalSettings,
+                headsUpManager,
+                keyguardNotificationVisibilityProvider,
+                keyguardStateController,
+                newLogger,
+                mainHandler,
+                powerManager,
+                statusBarStateController,
+                systemClock,
+                uiEventLogger,
+                userTracker
+            )
+        } else {
+            NotificationInterruptStateProviderWrapper(
+                NotificationInterruptStateProviderImpl(
+                    powerManager,
+                    ambientDisplayConfiguration,
+                    batteryController,
+                    statusBarStateController,
+                    keyguardStateController,
+                    headsUpManager,
+                    oldLogger,
+                    mainHandler,
+                    flags,
+                    keyguardNotificationVisibilityProvider,
+                    uiEventLogger,
+                    userTracker,
+                    deviceProvisionedController,
+                    systemClock,
+                    globalSettings,
+                    eventLog
+                )
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
index ca105f3..16c5c8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -15,7 +15,6 @@
 
 package com.android.systemui.statusbar.notification.shared
 
-import android.graphics.drawable.Icon
 import com.google.common.truth.Correspondence
 
 val byKey: Correspondence<ActiveNotificationModel, String> =
@@ -38,30 +37,3 @@
     )
 val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
     Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
-
-fun activeNotificationModel(
-    key: String,
-    groupKey: String? = null,
-    isAmbient: Boolean = false,
-    isRowDismissed: Boolean = false,
-    isSilent: Boolean = false,
-    isLastMessageFromReply: Boolean = false,
-    isSuppressedFromStatusBar: Boolean = false,
-    isPulsing: Boolean = false,
-    aodIcon: Icon? = null,
-    shelfIcon: Icon? = null,
-    statusBarIcon: Icon? = null,
-) =
-    ActiveNotificationModel(
-        key = key,
-        groupKey = groupKey,
-        isAmbient = isAmbient,
-        isRowDismissed = isRowDismissed,
-        isSilent = isSilent,
-        isLastMessageFromReply = isLastMessageFromReply,
-        isSuppressedFromStatusBar = isSuppressedFromStatusBar,
-        isPulsing = isPulsing,
-        aodIcon = aodIcon,
-        shelfIcon = shelfIcon,
-        statusBarIcon = statusBarIcon,
-    )
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 5903890..ff5c026 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
@@ -36,7 +36,6 @@
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
 
-import android.content.res.Resources;
 import android.metrics.LogMaker;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -53,7 +52,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -74,7 +72,6 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
@@ -84,17 +81,15 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 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.icon.ui.viewbinder.ShelfNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -170,8 +165,14 @@
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
+    private final ActiveNotificationListRepository mActiveNotificationsRepository =
+            new ActiveNotificationListRepository();
+
+    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+            new ActiveNotificationsInteractor(mActiveNotificationsRepository);
+
     private final SeenNotificationsInteractor mSeenNotificationsInteractor =
-            new SeenNotificationsInteractor(new ActiveNotificationListRepository());
+            new SeenNotificationsInteractor(mActiveNotificationsRepository);
 
     private NotificationStackScrollLayoutController mController;
 
@@ -701,6 +702,7 @@
                 mUiEventLogger,
                 mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
+                mActiveNotificationsInteractor,
                 mSeenNotificationsInteractor,
                 mViewBinder,
                 mShadeController,
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 e91d6d7..ba5ba2c 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
@@ -19,6 +19,7 @@
 import static android.view.View.GONE;
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -162,7 +163,7 @@
         //  in the constructor.
         mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
         mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
-        mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
+        mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
         mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
 
         // Inject dependencies before initializing the layout
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 46e8453..ac20683 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -34,9 +34,11 @@
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
-import com.android.systemui.util.animation.FakeAnimationStatusRepository
+import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.TestScope
@@ -47,8 +49,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
-import java.time.Duration
-import java.util.Optional
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
new file mode 100644
index 0000000..f00abc9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -0,0 +1,313 @@
+/*
+ * 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(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import android.app.NotificationManager.Policy
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.common.domain.CommonDomainLayerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.res.R
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
+import com.android.systemui.unfold.UnfoldTransitionModule
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationListViewModelTest : SysuiTestCase() {
+
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                ActivatableNotificationViewModelModule::class,
+                CommonDomainLayerModule::class,
+                FooterViewModelModule::class,
+                HeadlessSystemUserModeModule::class,
+                UnfoldTransitionModule.Bindings::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<NotificationListViewModel> {
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+        val shadeRepository: FakeShadeRepository
+        val zenModeRepository: FakeZenModeRepository
+        val configurationController: FakeConfigurationController
+
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                featureFlags: FakeFeatureFlagsClassicModule,
+                mocks: TestMocksModule,
+            ): TestComponent
+        }
+    }
+
+    private val testComponent: TestComponent =
+        DaggerNotificationListViewModelTest_TestComponent.factory()
+            .create(
+                test = this,
+                featureFlags =
+                    FakeFeatureFlagsClassicModule {
+                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
+                    },
+                mocks = TestMocksModule()
+            )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
+    }
+
+    @Test
+    fun testIsImportantForAccessibility_falseWhenNoNotifs() =
+        testComponent.runTest {
+            val important by collectLastValue(underTest.isImportantForAccessibility)
+
+            // WHEN on lockscreen
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+            // AND has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            testScope.runCurrent()
+
+            // THEN not important
+            assertThat(important).isFalse()
+        }
+
+    @Test
+    fun testIsImportantForAccessibility_trueWhenNotifs() =
+        testComponent.runTest {
+            val important by collectLastValue(underTest.isImportantForAccessibility)
+
+            // WHEN on lockscreen
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+            // AND has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            runCurrent()
+
+            // THEN is important
+            assertThat(important).isTrue()
+        }
+
+    @Test
+    fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
+        testComponent.runTest {
+            val important by collectLastValue(underTest.isImportantForAccessibility)
+
+            // WHEN not on lockscreen
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            // AND has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            runCurrent()
+
+            // THEN is still important
+            assertThat(important).isTrue()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            runCurrent()
+
+            // THEN should show
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenNotifs() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            runCurrent()
+
+            // THEN should not show
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND quick settings are expanded
+            shadeRepository.legacyQsFullscreen.value = true
+            runCurrent()
+
+            // THEN should not show
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND quick settings are expanded
+            shadeRepository.setQsExpansion(1f)
+            // AND split shade is enabled
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            // THEN should show
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND transitioning to AOD
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                )
+            )
+            runCurrent()
+
+            // THEN should not show
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
+        testComponent.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND is on bouncer
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                testScope,
+            )
+            runCurrent()
+
+            // THEN should not show
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_true() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(hidden).isTrue()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_false() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+            runCurrent()
+
+            assertThat(hidden).isFalse()
+        }
+
+    @Test
+    fun testHasFilteredOutSeenNotifications_true() =
+        testComponent.runTest {
+            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+            runCurrent()
+
+            assertThat(hasFilteredNotifs).isTrue()
+        }
+
+    @Test
+    fun testHasFilteredOutSeenNotifications_false() =
+        testComponent.runTest {
+            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+            runCurrent()
+
+            assertThat(hasFilteredNotifs).isFalse()
+        }
+}
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 9c70c82..ac7c2aa 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
@@ -15,36 +15,36 @@
  *
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -52,45 +52,29 @@
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<SharedNotificationContainerViewModel> {
-
-        val configurationRepository: FakeConfigurationRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardInteractor: KeyguardInteractor
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val shadeRepository: FakeShadeRepository
-        val sharedNotificationContainerInteractor: SharedNotificationContainerInteractor
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    val kosmos =
+        testKosmos().apply {
+            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    val testScope = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardInteractor = kosmos.keyguardInteractor
+    val keyguardRootViewModel = kosmos.keyguardRootViewModel
+    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val shadeRepository = kosmos.shadeRepository
+    val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
 
-    private val testComponent: TestComponent =
-        DaggerSharedNotificationContainerViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks = TestMocksModule(),
-            )
+    val underTest = kosmos.sharedNotificationContainerViewModel
+
+    @Before
+    fun setUp() {
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+    }
 
     @Test
     fun validateMarginStartInSplitShade() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
@@ -103,7 +87,7 @@
 
     @Test
     fun validateMarginStart() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
@@ -116,7 +100,7 @@
 
     @Test
     fun validateMarginEnd() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -128,7 +112,7 @@
 
     @Test
     fun validateMarginBottom() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.dimen.notification_panel_margin_bottom, 50)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -140,7 +124,7 @@
 
     @Test
     fun validateMarginTopWithLargeScreenHeader() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 50)
             overrideResource(R.dimen.notification_panel_margin_top, 0)
@@ -154,7 +138,7 @@
 
     @Test
     fun validateMarginTop() =
-        testComponent.runTest {
+        testScope.runTest {
             overrideResource(R.bool.config_use_large_screen_shade_header, false)
             overrideResource(R.dimen.large_screen_shade_header_height, 50)
             overrideResource(R.dimen.notification_panel_margin_top, 0)
@@ -168,7 +152,7 @@
 
     @Test
     fun isOnLockscreen() =
-        testComponent.runTest {
+        testScope.runTest {
             val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -206,7 +190,7 @@
 
     @Test
     fun isOnLockscreenWithoutShade() =
-        testComponent.runTest {
+        testScope.runTest {
             val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
 
             // First on AOD
@@ -242,8 +226,8 @@
 
     @Test
     fun positionOnLockscreenNotInSplitShade() =
-        testComponent.runTest {
-            val position by collectLastValue(underTest.position)
+        testScope.runTest {
+            val position by collectLastValue(underTest.bounds)
 
             // When not in split shade
             overrideResource(R.bool.config_use_split_notification_shade, false)
@@ -253,17 +237,17 @@
             // Start on lockscreen
             showLockscreen()
 
-            keyguardInteractor.sharedNotificationContainerPosition.value =
-                SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
 
-            assertThat(position)
-                .isEqualTo(SharedNotificationContainerPosition(top = 1f, bottom = 2f))
+            assertThat(position).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
         }
 
     @Test
     fun positionOnLockscreenInSplitShade() =
-        testComponent.runTest {
-            val position by collectLastValue(underTest.position)
+        testScope.runTest {
+            val position by collectLastValue(underTest.bounds)
 
             // When in split shade
             overrideResource(R.bool.config_use_split_notification_shade, true)
@@ -273,19 +257,19 @@
             // Start on lockscreen
             showLockscreen()
 
-            keyguardInteractor.sharedNotificationContainerPosition.value =
-                SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
             runCurrent()
 
             // Top should be overridden to 0f
-            assertThat(position)
-                .isEqualTo(SharedNotificationContainerPosition(top = 0f, bottom = 2f))
+            assertThat(position).isEqualTo(NotificationContainerBounds(top = 0f, bottom = 2f))
         }
 
     @Test
-    fun positionOnShade() =
-        testComponent.runTest {
-            val position by collectLastValue(underTest.position)
+    fun boundsOnShade() =
+        testScope.runTest {
+            val bounds by collectLastValue(underTest.bounds)
 
             // Start on lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
@@ -293,16 +277,14 @@
             // When not in split shade
             sharedNotificationContainerInteractor.setTopPosition(10f)
 
-            assertThat(position)
-                .isEqualTo(
-                    SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true)
-                )
+            assertThat(bounds)
+                .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = true))
         }
 
     @Test
-    fun positionOnQS() =
-        testComponent.runTest {
-            val position by collectLastValue(underTest.position)
+    fun boundsOnQS() =
+        testScope.runTest {
+            val bounds by collectLastValue(underTest.bounds)
 
             // Start on lockscreen with shade expanded
             showLockscreenWithQSExpanded()
@@ -310,15 +292,13 @@
             // When not in split shade
             sharedNotificationContainerInteractor.setTopPosition(10f)
 
-            assertThat(position)
-                .isEqualTo(
-                    SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false)
-                )
+            assertThat(bounds)
+                .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = false))
         }
 
     @Test
     fun maxNotificationsOnLockscreen() =
-        testComponent.runTest {
+        testScope.runTest {
             var notificationCount = 10
             val maxNotifications by
                 collectLastValue(underTest.getMaxNotifications { notificationCount })
@@ -327,8 +307,9 @@
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.sharedNotificationContainerPosition.value =
-                SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
 
             assertThat(maxNotifications).isEqualTo(10)
 
@@ -340,7 +321,7 @@
 
     @Test
     fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
-        testComponent.runTest {
+        testScope.runTest {
             var notificationCount = 10
             val maxNotifications by
                 collectLastValue(underTest.getMaxNotifications { notificationCount })
@@ -349,8 +330,9 @@
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.sharedNotificationContainerPosition.value =
-                SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
 
             assertThat(maxNotifications).isEqualTo(10)
 
@@ -375,7 +357,7 @@
 
     @Test
     fun maxNotificationsOnShade() =
-        testComponent.runTest {
+        testScope.runTest {
             val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 })
 
             // Show lockscreen with shade expanded
@@ -383,14 +365,26 @@
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.sharedNotificationContainerPosition.value =
-                SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
 
             // -1 means No Limit
             assertThat(maxNotifications).isEqualTo(-1)
         }
 
-    private suspend fun TestComponent.showLockscreen() {
+    @Test
+    fun updateBounds_fromKeyguardRoot() =
+        testScope.runTest {
+            val bounds by collectLastValue(underTest.bounds)
+
+            val top = 123f
+            val bottom = 456f
+            keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom))
+        }
+
+    private suspend fun showLockscreen() {
         shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(0f)
         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
@@ -401,7 +395,7 @@
         )
     }
 
-    private suspend fun TestComponent.showLockscreenWithShadeExpanded() {
+    private suspend fun showLockscreenWithShadeExpanded() {
         shadeRepository.setLockscreenShadeExpansion(1f)
         shadeRepository.setQsExpansion(0f)
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
@@ -412,7 +406,7 @@
         )
     }
 
-    private suspend fun TestComponent.showLockscreenWithQSExpanded() {
+    private suspend fun showLockscreenWithQSExpanded() {
         shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(1f)
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e61b4f8..051a4c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -135,7 +135,7 @@
         MockitoAnnotations.initMocks(this);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
-        when(mKeyguardStateController.isFaceEnrolled()).thenReturn(true);
+        when(mKeyguardStateController.isFaceEnrolledAndEnabled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
         when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
                 .thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 6570724..e339636 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -16,23 +16,18 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
 import static android.provider.Settings.Global.HEADS_UP_ON;
 
+import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.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.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
@@ -48,8 +43,6 @@
 
 import android.app.ActivityManager;
 import android.app.IWallpaperManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.WallpaperManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -157,13 +150,14 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -218,7 +212,7 @@
     private CentralSurfacesImpl mCentralSurfaces;
     private FakeMetricsLogger mMetricsLogger;
     private PowerManager mPowerManager;
-    private TestableNotificationInterruptStateProviderImpl mNotificationInterruptStateProvider;
+    private VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
 
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
@@ -349,7 +343,7 @@
         mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
         // For the Shade to animate during the Back gesture, we must enable the animation flag.
         mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
-        mFeatureFlags.set(Flags.LIGHT_REVEAL_MIGRATION, true);
+        mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
         // Turn AOD on and toggle feature flag for jank fixes
         mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
@@ -361,24 +355,26 @@
 
         mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
 
-        mNotificationInterruptStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(
-                        mPowerManager,
+        mVisualInterruptionDecisionProvider =
+                VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag(
                         mAmbientDisplayConfiguration,
-                        mStatusBarStateController,
-                        mKeyguardStateController,
                         mBatteryController,
-                        mHeadsUpManager,
-                        mock(NotificationInterruptLogger.class),
-                        new Handler(TestableLooper.get(this).getLooper()),
-                        mock(NotifPipelineFlags.class),
-                        mock(KeyguardNotificationVisibilityProvider.class),
-                        mock(UiEventLogger.class),
-                        mUserTracker,
                         mDeviceProvisionedController,
-                        mFakeSystemClock,
+                        mFakeEventLog,
+                        mock(NotifPipelineFlags.class),
                         mFakeGlobalSettings,
-                        mFakeEventLog);
+                        mHeadsUpManager,
+                        mock(KeyguardNotificationVisibilityProvider.class),
+                        mKeyguardStateController,
+                        new Handler(TestableLooper.get(this).getLooper()),
+                        mock(VisualInterruptionDecisionLogger.class),
+                        mock(NotificationInterruptLogger.class),
+                        mPowerManager,
+                        mStatusBarStateController,
+                        mFakeSystemClock,
+                        mock(UiEventLogger.class),
+                        mUserTracker);
+        mVisualInterruptionDecisionProvider.start();
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -481,7 +477,7 @@
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
                 mNotificationGutsManager,
-                mNotificationInterruptStateProvider,
+                mVisualInterruptionDecisionProvider,
                 new ShadeExpansionStateManager(),
                 mKeyguardViewMediator,
                 new DisplayMetrics(),
@@ -692,92 +688,6 @@
     }
 
     @Test
-    public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
-        when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mStatusBarStateController.isDreaming()).thenReturn(false);
-
-        Notification n = new Notification.Builder(getContext(), "a")
-                .setGroup("a")
-                .setGroupSummary(true)
-                .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
-                .build();
-
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
-    }
-
-    @Test
-    public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
-        when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mStatusBarStateController.isDreaming()).thenReturn(false);
-
-        Notification n = new Notification.Builder(getContext(), "a")
-                .setGroup("a")
-                .setGroupSummary(true)
-                .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
-                .build();
-
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
-    }
-
-    @Test
-    public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
-        when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mStatusBarStateController.isDreaming()).thenReturn(false);
-
-        Notification n = new Notification.Builder(getContext(), "a").build();
-
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setChannel(new NotificationChannel("id", null, IMPORTANCE_HIGH))
-                .setNotification(n)
-                .setImportance(IMPORTANCE_HIGH)
-                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
-                .build();
-
-        assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
-    }
-
-    @Test
-    public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
-        when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mStatusBarStateController.isDreaming()).thenReturn(false);
-
-        Notification n = new Notification.Builder(getContext(), "a").build();
-
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
-    }
-
-    @Test
     public void testDump_DoesNotCrash() {
         mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index bd0dbee..91cbc32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -86,7 +86,7 @@
         featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
         whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
-        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
+        whenever(keyguardStateController.isFaceEnrolledAndEnabled).thenReturn(true)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 361df1c..62a2bc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -60,7 +60,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.SceneTestUtils;
@@ -166,7 +165,6 @@
                 mKeyguardRepository,
                 mCommandQueue,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mFeatureFlags,
                 mSceneTestUtils.getSceneContainerFlags(),
                 new FakeKeyguardBouncerRepository(),
                 new FakeConfigurationRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
index 287ebba..bde2243 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
@@ -54,7 +54,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
-public class LightsOutNotifControllerTest extends SysuiTestCase {
+public class LegacyLightsOutNotifControllerTest extends SysuiTestCase {
     private static final int LIGHTS_ON = 0;
     private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS;
 
@@ -68,7 +68,7 @@
     @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor;
 
     private View mLightsOutView;
-    private LightsOutNotifController mLightsOutNotifController;
+    private LegacyLightsOutNotifController mLightsOutNotifController;
     private int mDisplayId;
     private Observer<Boolean> mHaActiveNotifsObserver;
     private CommandQueue.Callbacks mCallbacks;
@@ -83,7 +83,7 @@
         when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs);
         when(mHasActiveNotifs.getValue()).thenReturn(false);
 
-        mLightsOutNotifController = new LightsOutNotifController(
+        mLightsOutNotifController = new LegacyLightsOutNotifController(
                 mLightsOutView,
                 mWindowManager,
                 mNotifLiveDataStore,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index c45ecf3..f6419a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -121,7 +121,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -142,7 +142,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -165,7 +165,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -190,7 +190,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -214,7 +214,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -231,7 +231,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -249,7 +249,7 @@
                 new AppearanceRegion(0, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -266,7 +266,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -276,7 +276,7 @@
         reset(mStatusBarIconController);
 
         // WHEN the same appearance regions but different status bar mode is sent
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.LIGHTS_OUT_TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -298,7 +298,7 @@
                 /* start= */ new Rect(0, 0, 10, 10),
                 /* end= */ new Rect(0, 0, 20, 20));
 
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         startingBounds,
@@ -311,7 +311,7 @@
         BoundsPair newBounds = new BoundsPair(
                 /* start= */ new Rect(0, 0, 30, 30),
                 /* end= */ new Rect(0, 0, 40, 40));
-        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         newBounds,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 15c09b5..4827c92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1185,14 +1185,11 @@
     }
 
     @Test
-    public void testScrimFocus() {
-        mScrimController.transitionTo(ScrimState.AOD);
-        assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable());
-        assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable());
-
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-        Assert.assertTrue("Should be focusable on keyguard", mScrimBehind.isFocusable());
-        Assert.assertTrue("Should be focusable on keyguard", mScrimInFront.isFocusable());
+    public void testScrimsAreNotFocusable() {
+        assertFalse("Behind scrim should not be focusable", mScrimBehind.isFocusable());
+        assertFalse("Front scrim should not be focusable", mScrimInFront.isFocusable());
+        assertFalse("Notifications scrim should not be focusable",
+                mNotificationsScrim.isFocusable());
     }
 
     @Test
@@ -1263,14 +1260,6 @@
     }
 
     @Test
-    public void testViewsDontHaveFocusHighlight() {
-        assertFalse("Scrim shouldn't have focus highlight",
-                mScrimInFront.getDefaultFocusHighlightEnabled());
-        assertFalse("Scrim shouldn't have focus highlight",
-                mScrimBehind.getDefaultFocusHighlightEnabled());
-    }
-
-    @Test
     public void testIsLowPowerMode() {
         HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
                 ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index d1423e1..6cc4e44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
@@ -139,8 +138,6 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    @Mock
     private Handler mHandler;
     @Mock
     private BubblesManager mBubblesManager;
@@ -246,7 +243,6 @@
                         mock(NotificationLockscreenUserManager.class),
                         mShadeController,
                         mKeyguardStateController,
-                        mNotificationInterruptStateProvider,
                         mock(LockPatternUtils.class),
                         mock(StatusBarRemoteInputCallback.class),
                         mActivityIntentHelper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 53c621d..bbdc9ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -16,22 +16,28 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
+import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
@@ -55,7 +61,10 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -64,10 +73,14 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.List;
+import java.util.Set;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper()
@@ -76,18 +89,23 @@
     private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider =
             mock(VisualInterruptionDecisionProvider.class);
     private NotificationInterruptSuppressor mInterruptSuppressor;
+    private VisualInterruptionCondition mAlertsDisabledCondition;
+    private VisualInterruptionCondition mVrModeCondition;
+    private VisualInterruptionFilter mNeedsRedactionFilter;
+    private VisualInterruptionCondition mPanelsDisabledCondition;
     private CommandQueue mCommandQueue;
-    private FakeMetricsLogger mMetricsLogger;
     private final ShadeController mShadeController = mock(ShadeController.class);
     private final NotificationAlertsInteractor mNotificationAlertsInteractor =
             mock(NotificationAlertsInteractor.class);
     private final KeyguardStateController mKeyguardStateController =
             mock(KeyguardStateController.class);
-    private final InitController mInitController = new InitController();
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+            SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
 
     @Before
     public void setup() {
-        mMetricsLogger = new FakeMetricsLogger();
         mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mock(SysuiStatusBarStateController.class));
@@ -95,15 +113,182 @@
         mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
 
-        NotificationShadeWindowView notificationShadeWindowView =
+        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true);
+    }
+
+    @Test
+    public void testInit_refactorDisabled() {
+        ensureRefactorDisabledState();
+    }
+
+    @Test
+    public void testInit_refactorEnabled() {
+        ensureRefactorEnabledState();
+    }
+
+    @Test
+    public void testNoSuppressHeadsUp_default_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
+    }
+
+    @Test
+    public void testNoSuppressHeadsUp_default_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        assertFalse(mAlertsDisabledCondition.shouldSuppress());
+        assertFalse(mVrModeCondition.shouldSuppress());
+        assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry()));
+        assertFalse(mAlertsDisabledCondition.shouldSuppress());
+    }
+
+    @Test
+    public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
+                false /* animate */);
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue("The panel should suppress heads up while disabled",
+                mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
+    }
+
+    @Test
+    public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
+                false /* animate */);
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue("The panel should suppress heads up while disabled",
+                mPanelsDisabledCondition.shouldSuppress());
+    }
+
+    @Test
+    public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+                false /* animate */);
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue("The panel should suppress interruptions while notification shade disabled",
+                mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
+    }
+
+    @Test
+    public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+                false /* animate */);
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue("The panel should suppress interruptions while notification shade disabled",
+                mPanelsDisabledCondition.shouldSuppress());
+    }
+
+    @Test
+    public void testPanelsDisabledConditionSuppressesPeek() {
+        ensureRefactorEnabledState();
+
+        final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes();
+        assertTrue(types.contains(PEEK));
+        assertFalse(types.contains(PULSE));
+        assertFalse(types.contains(BUBBLE));
+    }
+
+    @Test
+    public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createFsiNotificationEntry()));
+    }
+
+    @Test
+    public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+        assertFalse(mNeedsRedactionFilter.shouldSuppress(createFsiNotificationEntry()));
+
+        final Set<VisualInterruptionType> types = mNeedsRedactionFilter.getTypes();
+        assertTrue(types.contains(PEEK));
+        assertFalse(types.contains(PULSE));
+        assertFalse(types.contains(BUBBLE));
+    }
+
+    @Test
+    public void testSuppressInterruptions_vrMode_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        mStatusBarNotificationPresenter.mVrMode = true;
+
+        assertTrue("Vr mode should suppress interruptions",
+                mInterruptSuppressor.suppressAwakeInterruptions(createNotificationEntry()));
+    }
+
+    @Test
+    public void testSuppressInterruptions_vrMode_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        mStatusBarNotificationPresenter.mVrMode = true;
+
+        assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress());
+
+        final Set<VisualInterruptionType> types = mVrModeCondition.getTypes();
+        assertTrue(types.contains(PEEK));
+        assertFalse(types.contains(PULSE));
+        assertTrue(types.contains(BUBBLE));
+    }
+
+    @Test
+    public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() {
+        ensureRefactorDisabledState();
+
+        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+
+        assertTrue("When alerts aren't enabled, interruptions are suppressed",
+                mInterruptSuppressor.suppressInterruptions(createNotificationEntry()));
+    }
+
+    @Test
+    public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() {
+        ensureRefactorEnabledState();
+
+        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+
+        assertTrue("When alerts aren't enabled, interruptions are suppressed",
+                mAlertsDisabledCondition.shouldSuppress());
+
+        final Set<VisualInterruptionType> types = mAlertsDisabledCondition.getTypes();
+        assertTrue(types.contains(PEEK));
+        assertTrue(types.contains(PULSE));
+        assertTrue(types.contains(BUBBLE));
+    }
+
+    private void createPresenter() {
+        final ShadeViewController shadeViewController = mock(ShadeViewController.class);
+
+        final NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
+        when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
+
         NotificationStackScrollLayoutController stackScrollLayoutController =
                 mock(NotificationStackScrollLayoutController.class);
         when(stackScrollLayoutController.getView()).thenReturn(
                 mock(NotificationStackScrollLayout.class));
-        when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
 
-        ShadeViewController shadeViewController = mock(ShadeViewController.class);
+        final InitController initController = new InitController();
+
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
                 mContext,
                 shadeViewController,
@@ -125,110 +310,76 @@
                 mock(NotifShadeEventSource.class),
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
-                mInitController,
+                initController,
                 mVisualInterruptionDecisionProvider,
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationRemoteInputManager.Callback.class),
                 mock(NotificationListContainer.class));
-        mInitController.executePostInitTasks();
-        ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
+
+        initController.executePostInitTasks();
+    }
+
+    private void verifyAndCaptureSuppressors() {
+        mInterruptSuppressor = null;
+
+        final ArgumentCaptor<VisualInterruptionCondition> conditionCaptor =
+                ArgumentCaptor.forClass(VisualInterruptionCondition.class);
+        verify(mVisualInterruptionDecisionProvider, times(3)).addCondition(
+                conditionCaptor.capture());
+        final List<VisualInterruptionCondition> conditions = conditionCaptor.getAllValues();
+        mAlertsDisabledCondition = conditions.get(0);
+        mVrModeCondition = conditions.get(1);
+        mPanelsDisabledCondition = conditions.get(2);
+
+        final ArgumentCaptor<VisualInterruptionFilter> needsRedactionFilterCaptor =
+                ArgumentCaptor.forClass(VisualInterruptionFilter.class);
+        verify(mVisualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture());
+        mNeedsRedactionFilter = needsRedactionFilterCaptor.getValue();
+    }
+
+    private void verifyAndCaptureLegacySuppressor() {
+        mAlertsDisabledCondition = null;
+        mVrModeCondition = null;
+        mNeedsRedactionFilter = null;
+        mPanelsDisabledCondition = null;
+
+        final ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
                 ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
         verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture());
         mInterruptSuppressor = suppressorCaptor.getValue();
     }
 
-    @Test
-    public void testNoSuppressHeadsUp_default() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
+    private void ensureRefactorDisabledState() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR);
+        createPresenter();
+        verifyAndCaptureLegacySuppressor();
+    }
+
+    private void ensureRefactorEnabledState() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR);
+        createPresenter();
+        verifyAndCaptureSuppressors();
+    }
+
+    private NotificationEntry createNotificationEntry() {
+        return new NotificationEntryBuilder()
                 .setPkg("a")
                 .setOpPkg("a")
                 .setTag("a")
-                .setNotification(n)
+                .setNotification(new Notification.Builder(getContext(), "a").build())
                 .build();
-
-        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
     }
 
-    @Test
-    public void testSuppressHeadsUp_disabledStatusBar() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress heads up while disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(entry));
-    }
-
-    @Test
-    public void testSuppressHeadsUp_disabledNotificationShade() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress interruptions while notification shade "
-                        + "disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(entry));
-    }
-
-    @Test
-    public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard() {
-        Notification n = new Notification.Builder(getContext(), "a")
+    private NotificationEntry createFsiNotificationEntry() {
+        final Notification notification = new Notification.Builder(getContext(), "a")
                 .setFullScreenIntent(mock(PendingIntent.class), true)
                 .build();
-        NotificationEntry entry = new NotificationEntryBuilder()
+
+        return new NotificationEntryBuilder()
                 .setPkg("a")
                 .setOpPkg("a")
                 .setTag("a")
-                .setNotification(n)
+                .setNotification(notification)
                 .build();
-
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
-        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
-    }
-
-    @Test
-    public void testSuppressInterruptions_vrMode() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        mStatusBarNotificationPresenter.mVrMode = true;
-
-        assertTrue("Vr mode should suppress interruptions",
-                mInterruptSuppressor.suppressAwakeInterruptions(entry));
-    }
-
-    @Test
-    public void testSuppressInterruptions_statusBarAlertsDisabled() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
-
-        assertTrue("When alerts aren't enabled, interruptions are suppressed",
-                mInterruptSuppressor.suppressInterruptions(entry));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
new file mode 100644
index 0000000..5a0e13d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.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
+ *
+ *      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.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class LightsOutInteractorTest : SysuiTestCase() {
+
+    private val statusBarModeRepository = FakeStatusBarModeRepository()
+    private val interactor: LightsOutInteractor = LightsOutInteractor(statusBarModeRepository)
+
+    @Test
+    fun isLowProfile_lightsOutStatusBarMode_false() = runTest {
+        statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.LIGHTS_OUT
+
+        val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+        assertThat(actual).isTrue()
+    }
+
+    @Test
+    fun isLowProfile_lightsOutTransparentStatusBarMode_true() = runTest {
+        statusBarModeRepository.defaultDisplay.statusBarMode.value =
+            StatusBarMode.LIGHTS_OUT_TRANSPARENT
+
+        val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+        assertThat(actual).isTrue()
+    }
+
+    @Test
+    fun isLowProfile_transparentStatusBarMode_false() = runTest {
+        statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+
+        val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+        assertThat(actual).isFalse()
+    }
+}
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 0b87fe8..17c2938 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
@@ -46,7 +46,6 @@
 import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -695,7 +694,6 @@
                 mLocationPublisher,
                 mMockNotificationAreaController,
                 mShadeExpansionStateManager,
-                mock(FeatureFlagsClassic.class),
                 mStatusBarIconController,
                 mIconManagerFactory,
                 mCollapsedStatusBarViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 49de512..7b73528c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -549,7 +549,7 @@
     @Test
     fun fullscreenIsTrue_chipStillClickable() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-        statusBarModeRepository.isInFullscreenMode.value = true
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
         testScope.runCurrent()
 
         assertThat(chipView.hasOnClickListeners()).isTrue()
@@ -559,7 +559,7 @@
 
     @Test
     fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
-        statusBarModeRepository.isInFullscreenMode.value = true
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
         testScope.runCurrent()
 
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -570,7 +570,7 @@
 
     @Test
     fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
-        statusBarModeRepository.isInFullscreenMode.value = false
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
         testScope.runCurrent()
 
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -583,7 +583,7 @@
     fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
-        statusBarModeRepository.isInFullscreenMode.value = true
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
         testScope.runCurrent()
 
         verify(mockSwipeStatusBarAwayGestureHandler)
@@ -592,11 +592,11 @@
 
     @Test
     fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
-        statusBarModeRepository.isInFullscreenMode.value = true
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         reset(mockSwipeStatusBarAwayGestureHandler)
 
-        statusBarModeRepository.isInFullscreenMode.value = false
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
         testScope.runCurrent()
 
         verify(mockSwipeStatusBarAwayGestureHandler)
@@ -605,7 +605,7 @@
 
     @Test
     fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
-        statusBarModeRepository.isInFullscreenMode.value = true
+        statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
         testScope.runCurrent()
         val ongoingCallNotifEntry = createOngoingCallNotifEntry()
         notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index e91b0c1..1fb6e2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -25,6 +25,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -68,6 +70,9 @@
 class FullMobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: FullMobileConnectionRepository
 
+    private val flags =
+        FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     private val systemClock = FakeSystemClock()
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -690,6 +695,7 @@
                 testDispatcher,
                 logger = mock(),
                 tableLogBuffer,
+                flags,
                 testScope.backgroundScope,
             )
         whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index a90bd48..9d6f315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -35,7 +35,9 @@
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
 import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
@@ -65,6 +67,8 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -111,6 +115,9 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    private val flags =
+        FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
@@ -158,6 +165,7 @@
                 testDispatcher,
                 logger,
                 tableLogger,
+                flags,
                 testScope.backgroundScope,
             )
     }
@@ -610,8 +618,80 @@
         }
 
     @Test
-    fun roaming_gsm_queriesServiceState() =
+    fun roaming_gsm_queriesDisplayInfo_viaDisplayInfo() =
         testScope.runTest {
+            // GIVEN flag is true
+            flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+
+            // Re-create the repository, because the flag is read at init
+            underTest =
+                MobileConnectionRepositoryImpl(
+                    SUB_1_ID,
+                    context,
+                    subscriptionModel,
+                    DEFAULT_NAME_MODEL,
+                    SEP,
+                    connectivityManager,
+                    telephonyManager,
+                    systemUiCarrierConfig,
+                    fakeBroadcastDispatcher,
+                    mobileMappings,
+                    testDispatcher,
+                    logger,
+                    tableLogger,
+                    flags,
+                    testScope.backgroundScope,
+                )
+
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            val cb = getTelephonyCallbackForType<DisplayInfoListener>()
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onDisplayInfoChanged(
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+            )
+
+            assertThat(latest).isFalse()
+
+            // CDMA roaming is off, GSM roaming is on
+            cb.onDisplayInfoChanged(
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+            )
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun roaming_gsm_queriesDisplayInfo_viaServiceState() =
+        testScope.runTest {
+            // GIVEN flag is false
+            flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
+
+            // Re-create the repository, because the flag is read at init
+            underTest =
+                MobileConnectionRepositoryImpl(
+                    SUB_1_ID,
+                    context,
+                    subscriptionModel,
+                    DEFAULT_NAME_MODEL,
+                    SEP,
+                    connectivityManager,
+                    telephonyManager,
+                    systemUiCarrierConfig,
+                    fakeBroadcastDispatcher,
+                    mobileMappings,
+                    testDispatcher,
+                    logger,
+                    tableLogger,
+                    flags,
+                    testScope.backgroundScope,
+                )
+
             var latest: Boolean? = null
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 889f60a..2ab8c0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -32,6 +32,8 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -97,6 +99,9 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    private val flags =
+        FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
@@ -139,6 +144,7 @@
                 testDispatcher,
                 logger,
                 tableLogger,
+                flags,
                 testScope.backgroundScope,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 03f3005..07abd27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -44,6 +44,8 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -93,6 +95,9 @@
 @TestableLooper.RunWithLooper
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
 
+    private val flags =
+        FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
     private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
@@ -189,6 +194,7 @@
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
                 scope = testScope.backgroundScope,
+                flags = flags,
                 carrierConfigRepository = carrierConfigRepository,
             )
         carrierMergedFactory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index cf815c2..ec04da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -50,10 +50,7 @@
     }
 
     fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) =
-        mock<TelephonyDisplayInfo>().also {
-            whenever(it.networkType).thenReturn(networkType)
-            whenever(it.overrideNetworkType).thenReturn(overrideNetworkType)
-        }
+        TelephonyDisplayInfo(networkType, overrideNetworkType)
 
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 688f739..09dc1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -17,49 +17,77 @@
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
 import androidx.test.filters.SmallTest
+import com.android.systemui.CoroutineTestScopeModule
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectValues
+import com.android.systemui.collectLastValue
+import com.android.systemui.collectValues
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.runTest
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
 
-    private lateinit var underTest: CollapsedStatusBarViewModel
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
+        val statusBarModeRepository: FakeStatusBarModeRepository
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
 
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var testScope: TestScope
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                testScope: CoroutineTestScopeModule,
+            ): TestComponent
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val testComponent: TestComponent =
+        DaggerCollapsedStatusBarViewModelImplTest_TestComponent.factory()
+            .create(
+                test = this,
+                testScope = CoroutineTestScopeModule(TestScope(UnconfinedTestDispatcher())),
+            )
 
     @Before
     fun setUp() {
-        testScope = TestScope(UnconfinedTestDispatcher())
-
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-        val interactor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = TestScope().backgroundScope,
-                    repository = keyguardTransitionRepository,
-                )
-                .keyguardTransitionInteractor
-        underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR)
     }
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -77,8 +105,8 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_running_isTrue() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -96,13 +124,13 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_finished_isFalse() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
-                this.testScheduler,
+                testScope.testScheduler,
             )
 
             assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse()
@@ -112,8 +140,8 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -131,8 +159,8 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -150,8 +178,8 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() =
-        testScope.runTest {
-            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+        testComponent.runTest {
+            val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -182,7 +210,7 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_started_emitted() =
-        testScope.runTest {
+        testComponent.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -199,7 +227,7 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() =
-        testScope.runTest {
+        testComponent.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -234,7 +262,7 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() =
-        testScope.runTest {
+        testComponent.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -283,7 +311,7 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() =
-        testScope.runTest {
+        testComponent.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -300,7 +328,7 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() =
-        testScope.runTest {
+        testComponent.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -317,4 +345,65 @@
 
             assertThat(emissions).isEmpty()
         }
+
+    @Test
+    fun areNotificationsLightsOut_lowProfileWithNotifications_true() =
+        testComponent.runTest {
+            statusBarModeRepository.defaultDisplay.statusBarMode.value =
+                StatusBarMode.LIGHTS_OUT_TRANSPARENT
+            activeNotificationListRepository.activeNotifications.value =
+                activeNotificationsStore(testNotifications)
+
+            val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() =
+        testComponent.runTest {
+            statusBarModeRepository.defaultDisplay.statusBarMode.value =
+                StatusBarMode.LIGHTS_OUT_TRANSPARENT
+            activeNotificationListRepository.activeNotifications.value =
+                activeNotificationsStore(emptyList())
+
+            val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() =
+        testComponent.runTest {
+            statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+            activeNotificationListRepository.activeNotifications.value =
+                activeNotificationsStore(emptyList())
+
+            val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() =
+        testComponent.runTest {
+            statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+            activeNotificationListRepository.activeNotifications.value =
+                activeNotificationsStore(testNotifications)
+
+            val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+            assertThat(actual).isFalse()
+        }
+
+    private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
+        ActiveNotificationsStore.Builder()
+            .apply { notifications.forEach(::addIndividualNotif) }
+            .build()
+
+    private val testNotifications =
+        listOf(
+            activeNotificationModel(key = "notif1"),
+            activeNotificationModel(key = "notif2"),
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index 88587b2..bc50f79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -16,11 +16,20 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
+    private val areNotificationLightsOut = MutableStateFlow(false)
+
     override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
 
     override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
+
+    override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
+
+    fun setNotificationLightsOut(lightsOut: Boolean) {
+        areNotificationLightsOut.value = lightsOut
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5c960b6..01dad38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -100,16 +100,16 @@
     public void testFaceAuthEnrolleddChanged_calledWhenFaceEnrollmentStateChanges() {
         KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
 
-        when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(false);
         verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
         mKeyguardStateController.addCallback(callback);
-        assertThat(mKeyguardStateController.isFaceEnrolled()).isFalse();
+        assertThat(mKeyguardStateController.isFaceEnrolledAndEnabled()).isFalse();
 
-        when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
         mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged(
                 BiometricSourceType.FACE);
 
-        assertThat(mKeyguardStateController.isFaceEnrolled()).isTrue();
+        assertThat(mKeyguardStateController.isFaceEnrolledAndEnabled()).isTrue();
         verify(callback).onFaceEnrolledChanged();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 99e62ee..dbb1062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -128,8 +128,7 @@
         testComponent.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
-            repository.consolidatedNotificationPolicy.value =
-                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
             repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
             runCurrent()
 
@@ -141,8 +140,7 @@
         testComponent.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
-            repository.consolidatedNotificationPolicy.value =
-                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
+            repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
             repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
             runCurrent()
 
@@ -154,19 +152,10 @@
         testComponent.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
-            repository.consolidatedNotificationPolicy.value =
-                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
             repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
             runCurrent()
 
             assertThat(hidden).isTrue()
         }
 }
-
-fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) =
-    Policy(
-        /* priorityCategories = */ 0,
-        /* priorityCallSenders = */ 0,
-        /* priorityMessageSenders = */ 0,
-        /* suppressedVisualEffects = */ suppressedVisualEffects
-    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 96db09e..59bf9f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -55,7 +54,6 @@
             keyguardRepository,
             mock<CommandQueue>(),
             PowerInteractorFactory.create().powerInteractor,
-            FakeFeatureFlagsClassic(),
             sceneTestUtils.sceneContainerFlags,
             FakeKeyguardBouncerRepository(),
             FakeConfigurationRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index c454b45..1123688 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -61,10 +61,12 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.monet.Style;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.common.util.concurrent.MoreExecutors;
@@ -88,7 +90,10 @@
 
     private static final int USER_SYSTEM = UserHandle.USER_SYSTEM;
     private static final int USER_SECONDARY = 10;
-
+    @Mock
+    private JavaAdapter mJavaAdapter;
+    @Mock
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private ThemeOverlayController mThemeOverlayController;
     @Mock
     private Executor mBgExecutor;
@@ -150,11 +155,12 @@
                 .thenReturn(Color.YELLOW);
         when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
                 .thenReturn(Color.BLACK);
+
         mThemeOverlayController = new ThemeOverlayController(mContext,
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -736,7 +742,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -776,7 +782,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 7f990a4..f924134 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -26,7 +26,6 @@
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
@@ -106,8 +105,7 @@
             onActionStarted.run()
         }
 
-        val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = FakeFeatureFlags())
         val keyguardInteractor = withDeps.keyguardInteractor
         keyguardRepository = withDeps.repository
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 9fe2f56..14fb054 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -15,9 +15,10 @@
  */
 package com.android.systemui.unfold.progress
 
+import android.os.Handler
+import android.os.HandlerThread
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
@@ -26,6 +27,8 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
 import com.android.systemui.unfold.util.TestFoldStateProvider
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,16 +40,28 @@
     private val foldStateProvider: TestFoldStateProvider = TestFoldStateProvider()
     private val listener = TestUnfoldProgressListener()
     private lateinit var progressProvider: UnfoldTransitionProgressProvider
+    private val schedulerFactory =
+        mock<UnfoldFrameCallbackScheduler.Factory>().apply {
+            whenever(create()).then { UnfoldFrameCallbackScheduler() }
+        }
+    private val mockBgHandler = mock<Handler>()
+    private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper)
 
     @Before
     fun setUp() {
-        progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider)
+        progressProvider =
+            PhysicsBasedUnfoldTransitionProgressProvider(
+                context,
+                schedulerFactory,
+                foldStateProvider = foldStateProvider,
+                progressHandler = fakeHandler
+            )
         progressProvider.addCallback(listener)
     }
 
     @Test
     fun testUnfold_emitsIncreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
@@ -63,7 +78,7 @@
 
     @Test
     fun testUnfold_emitsFinishingEvent() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
@@ -77,7 +92,7 @@
 
     @Test
     fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
@@ -94,7 +109,7 @@
 
     @Test
     fun testFold_emitsDecreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
             { foldStateProvider.sendHingeAngleUpdate(170f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
@@ -110,7 +125,7 @@
 
     @Test
     fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -126,7 +141,7 @@
 
     @Test
     fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -144,9 +159,12 @@
         with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
     }
 
-    private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) {
+    private fun runOnProgressThreadWithInterval(
+        vararg blocks: () -> Unit,
+        intervalMillis: Long = 60,
+    ) {
         blocks.forEach {
-            InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
+            fakeHandler.post(it)
             Thread.sleep(intervalMillis)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index aa49287..552b60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
 import java.util.concurrent.Executor
@@ -105,16 +104,15 @@
 
         foldStateProvider =
             DeviceFoldStateProvider(
-                config,
-                testHingeAngleProvider,
-                screenOnStatusProvider,
-                foldProvider,
-                activityTypeProvider,
-                unfoldKeyguardVisibilityProvider,
-                rotationChangeProvider,
-                context,
-                context.mainExecutor,
-                handler
+                    config,
+                    context,
+                    screenOnStatusProvider,
+                    activityTypeProvider,
+                    unfoldKeyguardVisibilityProvider,
+                    foldProvider,
+                    testHingeAngleProvider,
+                    rotationChangeProvider,
+                    handler
             )
 
         foldStateProvider.addCallback(
@@ -151,6 +149,12 @@
             null
         }
 
+        whenever(handler.post(any<Runnable>())).then { invocationOnMock ->
+            val runnable = invocationOnMock.getArgument<Runnable>(0)
+            runnable.run()
+            null
+        }
+
         // By default, we're on launcher.
         setupForegroundActivityType(isHomeActivity = true)
         setIsLargeScreen(true)
@@ -171,7 +175,7 @@
     }
 
     @Test
-    fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() {
+    fun onUnfold_angleDecrBeforeInnerScrAvailable_emitsOnlyStartAndInnerScrAvailableEvents() {
         setFoldState(folded = true)
         foldUpdates.clear()
 
@@ -187,7 +191,7 @@
     }
 
     @Test
-    fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() {
+    fun onUnfold_angleDecrAfterInnerScrAvailable_emitsStartInnerScrAvailableAndStartClosingEvnts() {
         setFoldState(folded = true)
         foldUpdates.clear()
 
@@ -690,7 +694,7 @@
             callbacks.forEach { it.onFoldUpdated(isFolded) }
         }
 
-        fun getNumberOfCallbacks(): Int{
+        fun getNumberOfCallbacks(): Int {
             return callbacks.size
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 0d78ae9..abfff34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -23,8 +23,6 @@
 import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
@@ -322,8 +320,6 @@
         }
 
     private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(FACE_AUTH_REFACTOR, true)
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
@@ -332,7 +328,6 @@
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
-            featureFlags = featureFlags,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 017eefe..bf851eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -121,8 +121,6 @@
         )
 
         utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-        utils.featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
-
         spyContext = spy(context)
         keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
         keyguardRepository = keyguardReply.repository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 7041eab..d1870b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -233,11 +233,7 @@
         }
 
     private fun viewModel(): StatusBarUserChipViewModel {
-        val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-                set(Flags.FACE_AUTH_REFACTOR, true)
-            }
+        val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         runBlocking {
             userRepository.setUserInfos(listOf(USER_0))
             userRepository.setSelectedUserInfo(USER_0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 686f492..b7b24f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -147,11 +147,7 @@
                 resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
 
-        val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-                set(Flags.FACE_AUTH_REFACTOR, true)
-            }
+        val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
         keyguardRepository = reply.repository
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt
new file mode 100644
index 0000000..080689a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.util.drawable
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.Drawable
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class LoopedAnimatable2DrawableWrapperTest : SysuiTestCase() {
+
+    @Mock private lateinit var drawable: AnimatedDrawable
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<Animatable2.AnimationCallback>
+
+    private lateinit var underTest: LoopedAnimatable2DrawableWrapper
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = LoopedAnimatable2DrawableWrapper.fromDrawable(drawable)
+    }
+
+    @Test
+    fun startAddsTheCallback() {
+        underTest.start()
+
+        verify(drawable).registerAnimationCallback(any())
+    }
+
+    @Test
+    fun multipleStartAddsTheCallbackOnce() {
+        underTest.start()
+        underTest.start()
+        underTest.start()
+        underTest.start()
+
+        verify(drawable).registerAnimationCallback(any())
+    }
+
+    @Test
+    fun stopRemovesTheCallback() {
+        underTest.start()
+
+        underTest.stop()
+
+        verify(drawable).unregisterAnimationCallback(any())
+    }
+
+    @Test
+    fun callbackSurvivesClearAnimationCallbacks() {
+        underTest.start()
+
+        underTest.clearAnimationCallbacks()
+
+        verify(drawable).clearAnimationCallbacks()
+        // start + re-add after #clearAnimationCallbacks
+        verify(drawable, times(2)).registerAnimationCallback(capture(callbackCaptor))
+    }
+
+    @Test
+    fun animationLooped() {
+        underTest.start()
+        verify(drawable).registerAnimationCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onAnimationEnd(drawable)
+
+        // underTest.start() + looped start()
+        verify(drawable, times(2)).start()
+    }
+
+    private abstract class AnimatedDrawable : Drawable(), Animatable2
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aa5f987..52c25f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -78,6 +78,7 @@
 import android.testing.TestableLooper;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.IWindowManager;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -144,7 +145,9 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
@@ -337,6 +340,8 @@
     private NotifPipelineFlags mNotifPipelineFlags;
     @Mock
     private Icon mAppBubbleIcon;
+    @Mock
+    private Display mDefaultDisplay;
 
     private final SceneTestUtils mUtils = new SceneTestUtils(this);
     private final TestScope mTestScope = mUtils.getTestScope();
@@ -378,6 +383,7 @@
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
         when(mNotificationShadeWindowView.getViewTreeObserver())
                 .thenReturn(mock(ViewTreeObserver.class));
+        when(mWindowManager.getDefaultDisplay()).thenReturn(mDefaultDisplay);
 
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
@@ -407,7 +413,6 @@
                 keyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                featureFlags,
                 sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
@@ -517,7 +522,8 @@
                     (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
         });
 
-        mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
+        mPositioner = new TestableBubblePositioner(mContext,
+                mContext.getSystemService(WindowManager.class));
         mPositioner.setMaxBubbles(5);
         mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController,
                 syncExecutor);
@@ -528,25 +534,26 @@
         final FakeGlobalSettings fakeGlobalSettings = new FakeGlobalSettings();
         fakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
 
-        TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(
-                        mock(PowerManager.class),
+        final VisualInterruptionDecisionProvider interruptionDecisionProvider =
+                VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag(
                         mock(AmbientDisplayConfiguration.class),
-                        mock(StatusBarStateController.class),
-                        mock(KeyguardStateController.class),
                         mock(BatteryController.class),
-                        mock(HeadsUpManager.class),
-                        mock(NotificationInterruptLogger.class),
-                        mock(Handler.class),
-                        mock(NotifPipelineFlags.class),
-                        mock(KeyguardNotificationVisibilityProvider.class),
-                        mock(UiEventLogger.class),
-                        mock(UserTracker.class),
                         mock(DeviceProvisionedController.class),
-                        mock(SystemClock.class),
+                        new FakeEventLog(),
+                        mock(NotifPipelineFlags.class),
                         fakeGlobalSettings,
-                        new FakeEventLog()
-                );
+                        mock(HeadsUpManager.class),
+                        mock(KeyguardNotificationVisibilityProvider.class),
+                        mock(KeyguardStateController.class),
+                        mock(Handler.class),
+                        mock(VisualInterruptionDecisionLogger.class),
+                        mock(NotificationInterruptLogger.class),
+                        mock(PowerManager.class),
+                        mock(StatusBarStateController.class),
+                        mock(SystemClock.class),
+                        mock(UiEventLogger.class),
+                        mock(UserTracker.class));
+        interruptionDecisionProvider.start();
 
         mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
                 mock(ShellCommandHandler.class),
@@ -595,7 +602,7 @@
                 mock(INotificationManager.class),
                 mIDreamManager,
                 mVisibilityProvider,
-                new NotificationInterruptStateProviderWrapper(interruptionStateProvider),
+                interruptionDecisionProvider,
                 mZenModeController,
                 mLockscreenUserManager,
                 mCommonNotifCollection,
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
similarity index 76%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
index 8e55695..f9c920a 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.app
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.activityManager by Kosmos.Fixture { mock<ActivityManager>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
similarity index 75%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
index 8e55695..b284ac0 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.app.admin
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<DevicePolicyManager>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
similarity index 65%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index 8e55695..f96c508 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.content
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+
+val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
index 8e55695..5686764 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.content.res
 
-import org.robolectric.annotation.GraphicsMode;
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.mainResources: Resources by Kosmos.Fixture { applicationContext.resources }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
similarity index 77%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
index 8e55695..c936b91 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.os
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.userManager by Kosmos.Fixture { mock<UserManager>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
index 8e55695..34c0a79 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package android.view
 
-import org.robolectric.annotation.GraphicsMode;
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.layoutInflater: LayoutInflater by
+    Kosmos.Fixture { LayoutInflater.from(applicationContext) }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
similarity index 67%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
index 8e55695..9059da2 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.internal.logging
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake }
+val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
index 8e55695..fadcecc 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.keyguard
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.keyguardSecurityModel by Kosmos.Fixture { mock<KeyguardSecurityModel>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
index 8e55695..b32cbe6 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.keyguard
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.keyguardUpdateMonitor by Kosmos.Fixture { mock<KeyguardUpdateMonitor>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
index 8e55695..4c4cfd5 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.guestResetOrExitSessionReceiver by
+    Kosmos.Fixture { mock<GuestResetOrExitSessionReceiver>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
index 8e55695..a9855ff 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.guestResumeSessionReceiver by Kosmos.Fixture { mock<GuestResumeSessionReceiver>() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/InstanceIdSequenceFake.kt
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/InstanceIdSequenceFake.kt
index 6fbe3ad..aae270d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/InstanceIdSequenceFake.kt
@@ -19,14 +19,10 @@
 import com.android.internal.logging.InstanceId
 import com.android.internal.logging.InstanceIdSequence
 
-/**
- * Fake [InstanceId] generator.
- */
+/** Fake [InstanceId] generator. */
 class InstanceIdSequenceFake(instanceIdMax: Int) : InstanceIdSequence(instanceIdMax) {
 
-    /**
-     * Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated.
-     */
+    /** Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated. */
     var lastInstanceId = -1
         private set
 
@@ -38,4 +34,4 @@
         }
         return newInstanceIdInternal(lastInstanceId)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index d0c1267..3724291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui
 
+import android.content.ContentResolver
 import android.content.Context
 import android.content.res.Resources
 import android.testing.TestableContext
@@ -80,6 +81,9 @@
             test.fakeBroadcastDispatcher
 
         @Provides
+        fun provideContentResolver(context: Context): ContentResolver = context.contentResolver
+
+        @Provides
         fun provideBaseShadeInteractor(
             sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
index 8e55695..46259a6 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+
+fun SysuiTestCase.testKosmos(): Kosmos = Kosmos().apply { testCase = this@testKosmos }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 37a4f61..f57ace9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -55,7 +55,9 @@
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.mock
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Binds
@@ -100,6 +102,10 @@
     @get:Provides val keyguardViewController: KeyguardViewController = mock(),
     @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
     @get:Provides val sysuiState: SysUiState = mock(),
+    @get:Provides
+    val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> =
+        Optional.empty(),
+    @get:Provides val zenModeController: ZenModeController = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt
new file mode 100644
index 0000000..ea93e94
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.authentication.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import kotlinx.coroutines.test.currentTime
+
+var Kosmos.authenticationRepository: AuthenticationRepository by
+    Kosmos.Fixture { fakeAuthenticationRepository }
+val Kosmos.fakeAuthenticationRepository by
+    Kosmos.Fixture { FakeAuthenticationRepository { testScope.currentTime } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
new file mode 100644
index 0000000..060ca4c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.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.authentication.domain.interactor
+
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.authenticationInteractor by
+    Kosmos.Fixture {
+        AuthenticationInteractor(
+            applicationScope = applicationCoroutineScope,
+            repository = authenticationRepository,
+            backgroundDispatcher = testDispatcher,
+            userRepository = userRepository,
+            clock = fakeSystemClock,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
index 8e55695..8702e00 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.biometrics.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
similarity index 66%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
index 8e55695..2a87074 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.bouncer.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.keyguardBouncerRepository: KeyguardBouncerRepository by
+    Kosmos.Fixture { fakeKeyguardBouncerRepository }
+val Kosmos.fakeKeyguardBouncerRepository by Kosmos.Fixture { FakeKeyguardBouncerRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastDispatcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastDispatcherKosmos.kt
new file mode 100644
index 0000000..7207948
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastDispatcherKosmos.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.systemui.broadcast
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.broadcastDispatcher by
+    Kosmos.Fixture {
+        FakeBroadcastDispatcher(
+            context = mock(),
+            mainExecutor = mock(),
+            broadcastRunningLooper = mock(),
+            broadcastRunningExecutor = mock(),
+            dumpManager = mock(),
+            logger = mock(),
+            userTracker = mock(),
+            shouldFailOnLeakedReceiver = false
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
similarity index 79%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
index 8e55695..3a72d11 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.classifier
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.falsingCollector by Kosmos.Fixture { FalsingCollectorFake() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
similarity index 60%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
index 8e55695..86a8ae5 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.common.ui
 
-import org.robolectric.annotation.GraphicsMode;
+import android.content.applicationContext
+import android.view.layoutInflater
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.configurationController
+
+val Kosmos.configurationState: ConfigurationState by
+    Kosmos.Fixture {
+        ConfigurationState(configurationController, applicationContext, layoutInflater)
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
similarity index 66%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
index 8e55695..77b8bd4 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.common.ui.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.configurationRepository: ConfigurationRepository by
+    Kosmos.Fixture { fakeConfigurationRepository }
+val Kosmos.fakeConfigurationRepository by Kosmos.Fixture { FakeConfigurationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 3aee889..faacce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -65,7 +65,7 @@
                 widgetRepository,
                 mediaRepository,
                 smartspaceRepository,
-                withDeps.communalTutorialInteractor,
+                withDeps.keyguardInteractor,
                 appWidgetHost,
                 editWidgetsActivityStarter,
             ),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index 36f0882..8c653a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -26,12 +26,14 @@
 import com.android.systemui.statusbar.data.FakeStatusBarDataLayerModule
 import com.android.systemui.telephony.data.FakeTelephonyDataLayerModule
 import com.android.systemui.user.data.FakeUserDataLayerModule
+import com.android.systemui.util.animation.data.FakeAnimationUtilDataLayerModule
 import dagger.Module
 
 @Module(
     includes =
         [
             FakeAccessibilityDataLayerModule::class,
+            FakeAnimationUtilDataLayerModule::class,
             FakeAuthenticationDataLayerModule::class,
             FakeBouncerDataLayerModule::class,
             FakeCommonDataLayerModule::class,
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
similarity index 67%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
index 8e55695..3da0681 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.deviceentry.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceEntryRepository: DeviceEntryRepository by
+    Kosmos.Fixture { fakeDeviceEntryRepository }
+val Kosmos.fakeDeviceEntryRepository by Kosmos.Fixture { FakeDeviceEntryRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
new file mode 100644
index 0000000..b600b50
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryInteractor by
+    Kosmos.Fixture {
+        DeviceEntryInteractor(
+            applicationScope = applicationCoroutineScope,
+            repository = deviceEntryRepository,
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+            deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
+            trustRepository = trustRepository,
+            flags = sceneContainerFlags,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt
new file mode 100644
index 0000000..b04161a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryUdfpsInteractor by Fixture {
+    DeviceEntryUdfpsInteractor(
+        fingerprintPropertyRepository = fingerprintPropertyRepository,
+        fingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+        biometricSettingsRepository = biometricSettingsRepository,
+    )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt
index 8e55695..0f7945f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.doze.util
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.burnInHelperWrapper by Fixture { BurnInHelperWrapper() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
similarity index 79%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 8e55695..e6b7f62 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.flags
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.featureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
index 8e55695..45d39b0 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
similarity index 64%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
index 8e55695..3d72967 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository by
+    Kosmos.Fixture { fakeDeviceEntryFaceAuthRepository }
+val Kosmos.fakeDeviceEntryFaceAuthRepository by
+    Kosmos.Fixture { FakeDeviceEntryFaceAuthRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
similarity index 70%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
index 8e55695..6437ef3 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+    FakeDeviceEntryFingerprintAuthRepository()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index df31a12..1381464 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -47,7 +47,7 @@
         get() = _isFaceAuthCurrentlyAllowed
 
     private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
-    override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
+    override val isFaceAuthSupportedInCurrentPosture: StateFlow<Boolean>
         get() = _isFaceAuthSupportedInCurrentPosture
 
     override val isCurrentUserInLockdown: Flow<Boolean>
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index d3744d55..4068e40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -167,6 +167,10 @@
         _isKeyguardOccluded.value = isOccluded
     }
 
+    fun setKeyguardUnlocked(isUnlocked: Boolean) {
+        _isKeyguardUnlocked.value = isUnlocked
+    }
+
     override fun setIsDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
index 8e55695..b0e4ba0 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inWindowLauncherUnlockAnimationRepository by
+    Kosmos.Fixture { InWindowLauncherUnlockAnimationRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
index 8e55695..453fef5 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardRepository: KeyguardRepository by Kosmos.Fixture { fakeKeyguardRepository }
+val Kosmos.fakeKeyguardRepository by Kosmos.Fixture { FakeKeyguardRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
similarity index 63%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
index 8e55695..c900ac9 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.keyguardSurfaceBehindRepository: KeyguardSurfaceBehindRepository by
+    Kosmos.Fixture { fakeKeyguardSurfaceBehindRepository }
+val Kosmos.fakeKeyguardSurfaceBehindRepository by
+    Kosmos.Fixture { FakeKeyguardSurfaceBehindRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 48d3742..008f79a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.shared.model
+package com.android.systemui.keyguard.data.repository
 
-/** Positioning info for the shared notification container */
-data class SharedNotificationContainerPosition(
-    val top: Float = 0f,
-    val bottom: Float = 0f,
+import com.android.systemui.kosmos.Kosmos
 
-    /** Whether any modifications to top/bottom are smoothly animated */
-    val animate: Boolean = false,
-)
+var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
+    Kosmos.Fixture { fakeKeyguardTransitionRepository }
+val Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
similarity index 70%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
index 45210ab..ca87acf 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.ui;
+package com.android.systemui.keyguard.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.trustRepository: TrustRepository by Kosmos.Fixture { fakeTrustRepository }
+val Kosmos.fakeTrustRepository by Kosmos.Fixture { FakeTrustRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
new file mode 100644
index 0000000..b0d941d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.doze.util.burnInHelperWrapper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.burnInInteractor by Fixture {
+    BurnInInteractor(
+        context = applicationContext,
+        burnInHelperWrapper = burnInHelperWrapper,
+        scope = applicationCoroutineScope,
+        configurationRepository = configurationRepository,
+        keyguardInteractor = keyguardInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..b03d0b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
+import dagger.Lazy
+
+val Kosmos.fromLockscreenTransitionInteractor by
+    Kosmos.Fixture {
+        FromLockscreenTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            keyguardInteractor = keyguardInteractor,
+            flags = featureFlagsClassic,
+            shadeRepository = shadeRepository,
+            powerInteractor = powerInteractor,
+            inWindowLauncherUnlockAnimationInteractor =
+                Lazy { inWindowLauncherUnlockAnimationInteractor },
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..ade3e1a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.keyguardSecurityModel
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.fromPrimaryBouncerTransitionInteractor by
+    Kosmos.Fixture {
+        FromPrimaryBouncerTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            keyguardInteractor = keyguardInteractor,
+            flags = featureFlagsClassic,
+            keyguardSecurityModel = keyguardSecurityModel,
+            selectedUserInteractor = selectedUserInteractor,
+            powerInteractor = powerInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..dbbb203
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shared.system.activityManagerWrapper
+import dagger.Lazy
+
+val Kosmos.inWindowLauncherUnlockAnimationInteractor by
+    Kosmos.Fixture {
+        InWindowLauncherUnlockAnimationInteractor(
+            repository = inWindowLauncherUnlockAnimationRepository,
+            scope = applicationCoroutineScope,
+            transitionInteractor = keyguardTransitionInteractor,
+            surfaceBehindRepository = Lazy { keyguardSurfaceBehindRepository },
+            activityManager = activityManagerWrapper,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index fc34903..3d8ae1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -20,6 +20,7 @@
 import android.os.Handler
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
@@ -80,15 +81,18 @@
                 trustRepository,
                 testScope.backgroundScope,
                 mock(SelectedUserInteractor::class.java),
+                mock(KeyguardFaceAuthInteractor::class.java),
             )
         val alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 mock(StatusBarStateController::class.java),
                 mock(KeyguardStateController::class.java),
                 bouncerRepository,
+                FakeFingerprintPropertyRepository(),
                 FakeBiometricSettingsRepository(),
                 FakeSystemClock(),
                 keyguardUpdateMonitor,
+                testScope.backgroundScope,
             )
         val powerInteractorWithDeps =
             PowerInteractorFactory.create(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index d2ff9bc5..c575bb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -40,7 +39,7 @@
     @JvmOverloads
     @JvmStatic
     fun create(
-        featureFlags: FakeFeatureFlags = createFakeFeatureFlags(),
+        featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
         sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
         repository: FakeKeyguardRepository = FakeKeyguardRepository(),
         commandQueue: FakeCommandQueue = FakeCommandQueue(),
@@ -62,7 +61,6 @@
             KeyguardInteractor(
                 repository = repository,
                 commandQueue = commandQueue,
-                featureFlags = featureFlags,
                 sceneContainerFlags = sceneContainerFlags,
                 bouncerRepository = bouncerRepository,
                 configurationRepository = configurationRepository,
@@ -73,11 +71,6 @@
         )
     }
 
-    /** Provide defaults, otherwise tests will throw an error */
-    private fun createFakeFeatureFlags(): FakeFeatureFlags {
-        return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
-    }
-
     data class WithDependencies(
         val repository: FakeKeyguardRepository,
         val commandQueue: FakeCommandQueue,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
new file mode 100644
index 0000000..bb84036
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.commandQueue
+
+val Kosmos.keyguardInteractor by
+    Kosmos.Fixture {
+        KeyguardInteractor(
+            repository = keyguardRepository,
+            commandQueue = commandQueue,
+            powerInteractor = powerInteractor,
+            sceneContainerFlags = sceneContainerFlags,
+            bouncerRepository = keyguardBouncerRepository,
+            configurationRepository = configurationRepository,
+            shadeRepository = shadeRepository,
+            sceneInteractorProvider = { sceneInteractor },
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..e4d115e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import dagger.Lazy
+
+val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
+    Kosmos.Fixture {
+        KeyguardTransitionInteractor(
+            scope = applicationCoroutineScope,
+            repository = keyguardTransitionRepository,
+            keyguardInteractor = Lazy { keyguardInteractor },
+            fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
+            fromPrimaryBouncerTransitionInteractor =
+                Lazy { fromPrimaryBouncerTransitionInteractor },
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..a31ab3e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
+    AodToLockscreenTransitionViewModel(
+        interactor = keyguardTransitionInteractor,
+        deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..5db95cf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.goneToAodTransitionViewModel by Fixture {
+    GoneToAodTransitionViewModel(
+        interactor = keyguardTransitionInteractor,
+        deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
new file mode 100644
index 0000000..663b845
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.burnInInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.keyguardRootViewModel by Fixture {
+    KeyguardRootViewModel(
+        context = applicationContext,
+        deviceEntryInteractor = deviceEntryInteractor,
+        dozeParameters = dozeParameters,
+        keyguardInteractor = keyguardInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+        notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+        burnInInteractor = burnInInteractor,
+        goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+        aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        screenOffAnimationController = screenOffAnimationController,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt
new file mode 100644
index 0000000..f533bca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.shadeDependentFlows by Fixture {
+    ShadeDependentFlows(
+        transitionInteractor = keyguardTransitionInteractor,
+        shadeInteractor = shadeInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index cc843b5..0b13858 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,11 @@
 package com.android.systemui.kosmos
 
+import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 
-val Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
-val Kosmos.testScope by Fixture { TestScope(testDispatcher) }
+var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
+var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
+var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
+var Kosmos.testCase: SysuiTestCase by Fixture()
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
similarity index 75%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
index 8e55695..0ec8d49 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.plugins
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 8e55695..cac2646 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.plugins.statusbar
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
similarity index 70%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
index 8e55695..c924579 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.power.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.powerRepository: PowerRepository by Kosmos.Fixture { fakePowerRepository }
+val Kosmos.fakePowerRepository by Kosmos.Fixture { FakePowerRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
new file mode 100644
index 0000000..8486691
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.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.systemui.power.domain.interactor
+
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.powerRepository
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+
+val Kosmos.powerInteractor by
+    Kosmos.Fixture {
+        PowerInteractor(
+            repository = powerRepository,
+            falsingCollector = falsingCollector,
+            screenOffAnimationController = screenOffAnimationController,
+            statusBarStateController = statusBarStateController,
+        )
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QsEventLoggerFake.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/QsEventLoggerFake.kt
index 40aa260..baccc6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QsEventLoggerFake.kt
@@ -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
  *
- *     http://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,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
new file mode 100644
index 0000000..1cb2587
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.qs
+
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
+    Kosmos.Fixture { InstanceIdSequenceFake(0) }
+val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
+val Kosmos.qsEventLogger: QsEventLoggerFake by
+    Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt
new file mode 100644
index 0000000..29702eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.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.systemui.qs.external
+
+import android.service.quicksettings.Tile
+
+class FakeCustomTileStatePersister : CustomTileStatePersister {
+
+    private val tiles: MutableMap<TileServiceKey, Tile> = mutableMapOf()
+
+    override fun readState(key: TileServiceKey): Tile? = tiles[key]
+
+    override fun persistState(key: TileServiceKey, tile: Tile) {
+        tiles[key] = tile
+    }
+
+    override fun removeState(key: TileServiceKey) {
+        tiles.remove(key)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
new file mode 100644
index 0000000..d2351dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import android.service.quicksettings.Tile
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.tiles
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+
+/**
+ * [Tile]-specific extension for [Truth]. Use [assertThat] or [tiles] to get an instance of this
+ * subject.
+ */
+class TileSubject private constructor(failureMetadata: FailureMetadata, subject: Tile?) :
+    Subject(failureMetadata, subject) {
+
+    private val actual: Tile? = subject
+
+    /** Asserts if the [Tile] fields are the same. */
+    fun isEqualTo(other: Tile?) {
+        if (actual == null) {
+            check("other").that(other).isNull()
+            return
+        } else {
+            check("other").that(other).isNotNull()
+            other ?: return
+        }
+
+        check("icon").that(actual.icon).isEqualTo(other.icon)
+        check("label").that(actual.label).isEqualTo(other.label)
+        check("subtitle").that(actual.subtitle).isEqualTo(other.subtitle)
+        check("contentDescription")
+            .that(actual.contentDescription)
+            .isEqualTo(other.contentDescription)
+        check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription)
+        check("activityLaunchForClick")
+            .that(actual.activityLaunchForClick)
+            .isEqualTo(other.activityLaunchForClick)
+        check("state").that(actual.state).isEqualTo(other.state)
+    }
+
+    companion object {
+
+        /** Returns a factory to be used with [Truth.assertAbout]. */
+        fun tiles(): Factory<TileSubject, Tile?> {
+            return Factory { failureMetadata: FailureMetadata, subject: Tile? ->
+                TileSubject(failureMetadata, subject)
+            }
+        }
+
+        /** Shortcut for `Truth.assertAbout(tiles()).that(tile)`. */
+        fun assertThat(tile: Tile?): TileSubject = Truth.assertAbout(tiles()).that(tile)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
index 13910fd..ccba072 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
@@ -19,15 +19,20 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
 
 class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository {
 
     private val defaults: MutableMap<DefaultsKey, CustomTileDefaults> = mutableMapOf()
-    private val defaultsFlow = MutableSharedFlow<DefaultsRequest>()
+    private val defaultsFlow =
+        MutableSharedFlow<DefaultsRequest>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST
+        )
 
     private val mutableDefaultsRequests: MutableList<DefaultsRequest> = mutableListOf()
     val defaultsRequests: List<DefaultsRequest> = mutableDefaultsRequests
@@ -41,7 +46,7 @@
                     old == new
                 }
             }
-            .map { defaults[DefaultsKey(it.user, it.componentName)]!! }
+            .mapNotNull { defaults[DefaultsKey(it.user, it.componentName)] }
 
     override fun requestNewDefaults(
         user: UserHandle,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
new file mode 100644
index 0000000..ccf0391
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.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.qs.tiles.impl.custom.data.repository
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+
+class FakeCustomTileRepository(
+    tileSpec: TileSpec.CustomTileSpec,
+    customTileStatePersister: FakeCustomTileStatePersister,
+    testBackgroundContext: CoroutineContext,
+) : CustomTileRepository {
+
+    private val realDelegate: CustomTileRepository =
+        CustomTileRepositoryImpl(
+            tileSpec,
+            customTileStatePersister,
+            testBackgroundContext,
+        )
+
+    override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) =
+        realDelegate.restoreForTheUserIfNeeded(user, isPersistable)
+
+    override fun getTiles(user: UserHandle): Flow<Tile> = realDelegate.getTiles(user)
+
+    override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
+
+    override suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    ) = realDelegate.updateWithTile(user, newTile, isPersistable)
+
+    override suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    ) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt
similarity index 65%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt
index 8e55695..97e9b51 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.qs.tiles.impl.flashlight
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsFlashlightTileConfig by
+    Kosmos.Fixture { PolicyModule.provideFlashlightTileConfig(qsEventLogger) }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt
similarity index 66%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt
index 8e55695..f4c7ca7 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.qs.tiles.impl.location
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsLocationTileConfig by
+    Kosmos.Fixture { PolicyModule.provideLocationTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 29e73b5..3c96051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -63,7 +63,9 @@
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -78,6 +80,9 @@
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -115,8 +120,8 @@
     val testScope = kosmos.testScope
     val featureFlags =
         FakeFeatureFlagsClassic().apply {
-            set(Flags.FACE_AUTH_REFACTOR, false)
             set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            set(Flags.NSSL_DEBUG_LINES, false)
         }
     val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
     val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
@@ -239,7 +244,6 @@
         return KeyguardInteractor(
             repository = repository,
             commandQueue = FakeCommandQueue(),
-            featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
             bouncerRepository = FakeKeyguardBouncerRepository(),
             configurationRepository = configurationRepository,
@@ -258,12 +262,14 @@
 
     fun bouncerInteractor(
         authenticationInteractor: AuthenticationInteractor,
+        keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor = mock(),
     ): BouncerInteractor {
         return BouncerInteractor(
             applicationScope = applicationScope(),
             applicationContext = context,
             repository = bouncerRepository,
             authenticationInteractor = authenticationInteractor,
+            keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
             flags = sceneContainerFlags,
             falsingInteractor = falsingInteractor(),
             powerInteractor = powerInteractor(),
@@ -271,6 +277,19 @@
         )
     }
 
+    fun keyguardRootViewModel(): KeyguardRootViewModel = mock()
+
+    fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
+        return NotificationsPlaceholderViewModel(
+            interactor =
+                NotificationStackAppearanceInteractor(
+                    repository = NotificationStackAppearanceRepository(),
+                ),
+            flags = sceneContainerFlags,
+            featureFlags = featureFlags,
+        )
+    }
+
     fun bouncerViewModel(
         bouncerInteractor: BouncerInteractor,
         authenticationInteractor: AuthenticationInteractor,
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
index 8e55695..7c4e160 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.scene.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.shared.model.sceneContainerConfig
+
+val Kosmos.sceneContainerRepository by
+    Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
new file mode 100644
index 0000000..9989876
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.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.systemui.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.logger.sceneLogger
+
+val Kosmos.sceneInteractor by
+    Kosmos.Fixture {
+        SceneInteractor(
+            applicationScope = applicationCoroutineScope,
+            repository = sceneContainerRepository,
+            powerInteractor = powerInteractor,
+            logger = sceneLogger,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
similarity index 77%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
index 8e55695..c2cdbed 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.scene.shared.flag
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
index 8e55695..c5f24f4 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.scene.shared.logger
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.sceneLogger by Kosmos.Fixture { mock<SceneLogger>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
index 8e55695..f9cdc1b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.scene.shared.model
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.sceneContainerConfig by
+    Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
similarity index 70%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
index 8e55695..38cedbc 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.shade.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.shadeRepository: ShadeRepository by Kosmos.Fixture { fakeShadeRepository }
+val Kosmos.fakeShadeRepository by Kosmos.Fixture { FakeShadeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
new file mode 100644
index 0000000..7da57f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.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.shade.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.ShadeModule
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+
+var Kosmos.baseShadeInteractor: BaseShadeInteractor by
+    Kosmos.Fixture {
+        ShadeModule.provideBaseShadeInteractor(
+            sceneContainerFlags = sceneContainerFlags,
+            sceneContainerOn = { shadeInteractorSceneContainerImpl },
+            sceneContainerOff = { shadeInteractorLegacyImpl },
+        )
+    }
+val Kosmos.shadeInteractorSceneContainerImpl by
+    Kosmos.Fixture {
+        ShadeInteractorSceneContainerImpl(
+            scope = applicationCoroutineScope,
+            sceneInteractor = sceneInteractor,
+            sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+        )
+    }
+val Kosmos.shadeInteractorLegacyImpl by
+    Kosmos.Fixture {
+        ShadeInteractorLegacyImpl(
+            scope = applicationCoroutineScope,
+            keyguardRepository = keyguardRepository,
+            sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+            repository = shadeRepository
+        )
+    }
+var Kosmos.shadeInteractor: ShadeInteractor by Kosmos.Fixture { shadeInteractorImpl }
+val Kosmos.shadeInteractorImpl by
+    Kosmos.Fixture {
+        ShadeInteractorImpl(
+            scope = applicationCoroutineScope,
+            deviceProvisioningRepository = deviceProvisioningRepository,
+            disableFlagsRepository = disableFlagsRepository,
+            dozeParams = dozeParameters,
+            keyguardRepository = fakeKeyguardRepository,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            powerInteractor = powerInteractor,
+            userSetupRepository = userSetupRepository,
+            userSwitcherInteractor = userSwitcherInteractor,
+            baseShadeInteractor = baseShadeInteractor,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
index 8e55695..e753593 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.shared.system
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.activityManagerWrapper by Kosmos.Fixture { mock<ActivityManagerWrapper>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
similarity index 75%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
index 8e55695..27f7f68 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index f25d282..6069083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -16,14 +16,33 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.google.common.truth.Truth.assertThat
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
+@SysUISingleton
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositoryStore {
+
+    companion object {
+        const val DISPLAY_ID = Display.DEFAULT_DISPLAY
+    }
+
+    override val defaultDisplay: FakeStatusBarModePerDisplayRepository =
+        FakeStatusBarModePerDisplayRepository()
+
+    override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
+        assertThat(displayId).isEqualTo(DISPLAY_ID)
+        return defaultDisplay
+    }
+}
+
+class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository {
     override val isTransientShown = MutableStateFlow(false)
     override val isInFullscreenMode = MutableStateFlow(false)
     override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
@@ -39,5 +58,5 @@
 
 @Module
 interface FakeStatusBarModeRepositoryModule {
-    @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+    @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepositoryStore
 }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
index 8e55695..10151ac 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationListenerSettingsRepository by
+    Kosmos.Fixture { NotificationListenerSettingsRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
similarity index 66%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
index 8e55695..a373a8e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.disableflags.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.disableFlagsRepository: DisableFlagsRepository by
+    Kosmos.Fixture { fakeDisableFlagsRepository }
+val Kosmos.fakeDisableFlagsRepository by Kosmos.Fixture { FakeDisableFlagsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
new file mode 100644
index 0000000..9851b0e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.notification.data.model
+
+import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+
+/** Simple ActiveNotificationModel builder for use in tests. */
+fun activeNotificationModel(
+    key: String,
+    groupKey: String? = null,
+    isAmbient: Boolean = false,
+    isRowDismissed: Boolean = false,
+    isSilent: Boolean = false,
+    isLastMessageFromReply: Boolean = false,
+    isSuppressedFromStatusBar: Boolean = false,
+    isPulsing: Boolean = false,
+    aodIcon: Icon? = null,
+    shelfIcon: Icon? = null,
+    statusBarIcon: Icon? = null,
+) =
+    ActiveNotificationModel(
+        key = key,
+        groupKey = groupKey,
+        isAmbient = isAmbient,
+        isRowDismissed = isRowDismissed,
+        isSilent = isSilent,
+        isLastMessageFromReply = isLastMessageFromReply,
+        isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+        isPulsing = isPulsing,
+        aodIcon = aodIcon,
+        shelfIcon = shelfIcon,
+        statusBarIcon = statusBarIcon,
+    )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
new file mode 100644
index 0000000..cb1ba20
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+
+/**
+ * Make the repository hold [count] active notifications for testing. The keys of the notifications
+ * are "0", "1", ..., (count - 1).toString().
+ */
+fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
+    this.activeNotifications.value =
+        ActiveNotificationsStore.Builder()
+            .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } }
+            .build()
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
similarity index 73%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
index 8e55695..5507d6c 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activeNotificationListRepository by Kosmos.Fixture { ActiveNotificationListRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
similarity index 71%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
index 8e55695..ed62fda 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.headsUpNotificationIconViewStateRepository by
+    Kosmos.Fixture { HeadsUpNotificationIconViewStateRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
index 48d3742..f2b9da4 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.shared.model
+package com.android.systemui.statusbar.notification.data.repository
 
-/** Positioning info for the shared notification container */
-data class SharedNotificationContainerPosition(
-    val top: Float = 0f,
-    val bottom: Float = 0f,
+import com.android.systemui.kosmos.Kosmos
 
-    /** Whether any modifications to top/bottom are smoothly animated */
-    val animate: Boolean = false,
-)
+var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
+    Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository }
+val Kosmos.fakeNotificationsKeyguardViewStateRepository by
+    Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
similarity index 63%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
index 8e55695..3d7fb6d 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.domain.interactor
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+
+val Kosmos.activeNotificationsInteractor by
+    Kosmos.Fixture { ActiveNotificationsInteractor(activeNotificationListRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
index 48d3742..d14c854 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.shared.model
+package com.android.systemui.statusbar.notification.domain.interactor
 
-/** Positioning info for the shared notification container */
-data class SharedNotificationContainerPosition(
-    val top: Float = 0f,
-    val bottom: Float = 0f,
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository
 
-    /** Whether any modifications to top/bottom are smoothly animated */
-    val animate: Boolean = false,
-)
+val Kosmos.headsUpNotificationIconInteractor by
+    Kosmos.Fixture { HeadsUpNotificationIconInteractor(headsUpNotificationIconViewStateRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
new file mode 100644
index 0000000..e7bd5ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * 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(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.wm.shell.bubbles.bubblesOptional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alwaysOnDisplayNotificationIconsInteractor by
+    Kosmos.Fixture {
+        AlwaysOnDisplayNotificationIconsInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
+            iconsInteractor = notificationIconsInteractor,
+        )
+    }
+val Kosmos.statusBarNotificationIconsInteractor by
+    Kosmos.Fixture {
+        StatusBarNotificationIconsInteractor(
+            iconsInteractor = notificationIconsInteractor,
+            settingsRepository = notificationListenerSettingsRepository,
+        )
+    }
+val Kosmos.notificationIconsInteractor by
+    Kosmos.Fixture {
+        NotificationIconsInteractor(
+            activeNotificationsInteractor = activeNotificationsInteractor,
+            bubbles = bubblesOptional,
+            keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt
new file mode 100644
index 0000000..6295b83
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.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.notification.icon.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor
+
+val Kosmos.notificationIconContainerAlwaysOnDisplayViewModel by
+    Kosmos.Fixture {
+        NotificationIconContainerAlwaysOnDisplayViewModel(
+            iconsInteractor = alwaysOnDisplayNotificationIconsInteractor,
+            keyguardInteractor = keyguardInteractor,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            resources = mainResources,
+            shadeInteractor = shadeInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt
new file mode 100644
index 0000000..04bb52d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.statusBarNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
+
+val Kosmos.notificationIconContainerStatusBarViewModel by
+    Kosmos.Fixture {
+        NotificationIconContainerStatusBarViewModel(
+            darkIconInteractor = darkIconInteractor,
+            iconsInteractor = statusBarNotificationIconsInteractor,
+            headsUpIconInteractor = headsUpNotificationIconInteractor,
+            keyguardInteractor = keyguardInteractor,
+            notificationsInteractor = activeNotificationsInteractor,
+            resources = mainResources,
+            shadeInteractor = shadeInteractor,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt
index 8e55695..4073902 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.notification.stack.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.notificationStackAppearanceRepository by Fixture {
+    NotificationStackAppearanceRepository()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt
new file mode 100644
index 0000000..546a1e0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.notification.stack.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.data.repository.notificationStackAppearanceRepository
+
+val Kosmos.notificationStackAppearanceInteractor by Fixture {
+    NotificationStackAppearanceInteractor(
+        repository = notificationStackAppearanceRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
new file mode 100644
index 0000000..61a38b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+
+val Kosmos.notificationsKeyguardInteractor by Fixture {
+    NotificationsKeyguardInteractor(
+        repository = notificationsKeyguardViewStateRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
new file mode 100644
index 0000000..3403227
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.sharedNotificationContainerInteractor by
+    Kosmos.Fixture {
+        SharedNotificationContainerInteractor(
+            configurationRepository = configurationRepository,
+            context = applicationContext,
+            splitShadeStateController = splitShadeStateController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
new file mode 100644
index 0000000..f2f3a5a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+
+val Kosmos.notificationStackAppearanceViewModel by Fixture {
+    NotificationStackAppearanceViewModel(
+        stackAppearanceInteractor = notificationStackAppearanceInteractor,
+        shadeInteractor = shadeInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
new file mode 100644
index 0000000..0dbade7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+
+val Kosmos.notificationsPlaceholderViewModel by Fixture {
+    NotificationsPlaceholderViewModel(
+        interactor = notificationStackAppearanceInteractor,
+        flags = sceneContainerFlags,
+        featureFlags = featureFlagsClassic,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
new file mode 100644
index 0000000..c17083c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.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.notification.stack.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+
+val Kosmos.sharedNotificationContainerViewModel by Fixture {
+    SharedNotificationContainerViewModel(
+        interactor = sharedNotificationContainerInteractor,
+        applicationScope = applicationCoroutineScope,
+        keyguardInteractor = keyguardInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+        shadeInteractor = shadeInteractor,
+    )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
similarity index 74%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
index 8e55695..9f6b181 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.phone
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.dozeParameters by Kosmos.Fixture { mock<DozeParameters>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
index 8e55695..d4c21f6 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.phone
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.screenOffAnimationController by Kosmos.Fixture { mock<ScreenOffAnimationController>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
index 8e55695..977dcb7 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.phone.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.darkIconRepository: DarkIconRepository by Kosmos.Fixture { fakeDarkIconRepository }
+val Kosmos.fakeDarkIconRepository by Kosmos.Fixture { FakeDarkIconRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
index 8e55695..db678d4 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.phone.domain.interactor
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.data.repository.darkIconRepository
+
+val Kosmos.darkIconInteractor by Kosmos.Fixture { DarkIconInteractor(darkIconRepository) }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
similarity index 67%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
index 8e55695..7b9634a 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
+val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
similarity index 72%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 8e55695..18a2f94 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.policy
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
similarity index 65%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
index 8e55695..6a77c88 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.policy
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceProvisionedController: DeviceProvisionedController by
+    Kosmos.Fixture { fakeDeviceProvisionedController }
+val Kosmos.fakeDeviceProvisionedController by Kosmos.Fixture { FakeDeviceProvisionedController() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
similarity index 65%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
index 8e55695..5e430381 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.policy
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.splitShadeStateController: SplitShadeStateController by
+    Kosmos.Fixture { resourcesSplitShadeStateController }
+val Kosmos.resourcesSplitShadeStateController by
+    Kosmos.Fixture { ResourcesSplitShadeStateController() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
similarity index 64%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
index 8e55695..56a0e02 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.statusbar.policy.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceProvisioningRepository: DeviceProvisioningRepository by
+    Kosmos.Fixture { fakeDeviceProvisioningRepository }
+val Kosmos.fakeDeviceProvisioningRepository by Kosmos.Fixture { FakeDeviceProvisioningRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
index 4059930..c4d7867 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.policy.data.repository
 
-import android.app.NotificationManager
+import android.app.NotificationManager.Policy
 import android.provider.Settings
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
@@ -27,14 +27,24 @@
 @SysUISingleton
 class FakeZenModeRepository @Inject constructor() : ZenModeRepository {
     override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF)
-    override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> =
+    override val consolidatedNotificationPolicy: MutableStateFlow<Policy?> =
         MutableStateFlow(
-            NotificationManager.Policy(
+            Policy(
                 /* priorityCategories = */ 0,
                 /* priorityCallSenders = */ 0,
                 /* priorityMessageSenders = */ 0,
             )
         )
+
+    fun setSuppressedVisualEffects(suppressedVisualEffects: Int) {
+        consolidatedNotificationPolicy.value =
+            Policy(
+                /* priorityCategories = */ 0,
+                /* priorityCallSenders = */ 0,
+                /* priorityMessageSenders = */ 0,
+                /* suppressedVisualEffects = */ suppressedVisualEffects,
+            )
+    }
 }
 
 @Module
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
index 8e55695..6bb5ec5 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.telephony.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.telephonyRepository: TelephonyRepository by Kosmos.Fixture { fakeTelephonyRepository }
+val Kosmos.fakeTelephonyRepository by Kosmos.Fixture { FakeTelephonyRepository() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
similarity index 69%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
index 8e55695..02ca96e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.telephony.domain.interactor
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.telephony.data.repository.telephonyRepository
+
+val Kosmos.telephonyInteractor by Kosmos.Fixture { TelephonyInteractor(telephonyRepository) }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
similarity index 70%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
index 8e55695..9bb5262 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.user.data.repository
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.userRepository: UserRepository by Kosmos.Fixture { fakeUserRepository }
+val Kosmos.fakeUserRepository by Kosmos.Fixture { FakeUserRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt
new file mode 100644
index 0000000..3b1c3f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.user.domain.interactor
+
+import android.app.admin.devicePolicyManager
+import android.content.applicationContext
+import android.os.userManager
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.guestResetOrExitSessionReceiver
+import com.android.systemui.guestResumeSessionReceiver
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.guestUserInteractor by
+    Kosmos.Fixture {
+        GuestUserInteractor(
+            applicationContext = applicationContext,
+            applicationScope = applicationCoroutineScope,
+            mainDispatcher = testDispatcher,
+            backgroundDispatcher = testDispatcher,
+            manager = userManager,
+            repository = userRepository,
+            deviceProvisionedController = deviceProvisionedController,
+            devicePolicyManager = devicePolicyManager,
+            refreshUsersScheduler = refreshUsersScheduler,
+            uiEventLogger = uiEventLogger,
+            resumeSessionReceiver = guestResumeSessionReceiver,
+            resetOrExitSessionReceiver = guestResetOrExitSessionReceiver,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
similarity index 76%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
index 8e55695..de9f69b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.user.domain.interactor
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.headlessSystemUserMode by Kosmos.Fixture { HeadlessSystemUserModeImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt
new file mode 100644
index 0000000..14da8b0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.user.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.refreshUsersScheduler by
+    Kosmos.Fixture {
+        RefreshUsersScheduler(applicationCoroutineScope, testDispatcher, userRepository)
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
similarity index 64%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
index 8e55695..427f92a 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.user.domain.interactor
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.selectedUserInteractor by
+    Kosmos.Fixture { SelectedUserInteractor(userRepository, featureFlagsClassic) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
new file mode 100644
index 0000000..42c77aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.user.domain.interactor
+
+import android.app.activityManager
+import android.content.applicationContext
+import android.os.userManager
+import com.android.internal.logging.uiEventLogger
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.utils.userRestrictionChecker
+
+val Kosmos.userSwitcherInteractor by
+    Kosmos.Fixture {
+        UserSwitcherInteractor(
+            applicationContext = applicationContext,
+            repository = userRepository,
+            activityStarter = activityStarter,
+            keyguardInteractor = keyguardInteractor,
+            featureFlags = featureFlagsClassic,
+            manager = userManager,
+            headlessSystemUserMode = headlessSystemUserMode,
+            applicationScope = applicationCoroutineScope,
+            telephonyInteractor = telephonyInteractor,
+            broadcastDispatcher = broadcastDispatcher,
+            keyguardUpdateMonitor = keyguardUpdateMonitor,
+            backgroundDispatcher = testDispatcher,
+            activityManager = activityManager,
+            refreshUsersScheduler = refreshUsersScheduler,
+            guestUserInteractor = guestUserInteractor,
+            uiEventLogger = uiEventLogger,
+            userRestrictionChecker = userRestrictionChecker,
+        )
+    }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
index 8e55695..f7830d9 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.util.animation.data
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeAnimationStatusRepositoryModule::class])
+object FakeAnimationUtilDataLayerModule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
index e72235c..ca6628b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt
@@ -11,15 +11,17 @@
  * 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
+ * limitations under the License.
  */
-package com.android.systemui.util.animation
+package com.android.systemui.util.animation.data.repository
 
-import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 
-class FakeAnimationStatusRepository : AnimationStatusRepository {
+class FakeAnimationStatusRepository @Inject constructor() : AnimationStatusRepository {
 
     // Replay 1 element as real repository always emits current status as a first element
     private val animationsEnabled: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1)
@@ -30,3 +32,8 @@
         animationsEnabled.tryEmit(enabled)
     }
 }
+
+@Module
+interface FakeAnimationStatusRepositoryModule {
+    @Binds fun bindFake(fake: FakeAnimationStatusRepository): AnimationStatusRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index db5eaff..beabaf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -38,7 +38,9 @@
 
     @Override
     public ContentResolver getContentResolver() {
-        return null;
+        throw new UnsupportedOperationException(
+                "GlobalSettings.getContentResolver is not implemented, but you may find "
+                        + "GlobalSettings.registerContentObserver helpful instead.");
     }
 
     @Override
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
similarity index 79%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 8e55695..914e654 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.util.time
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
similarity index 78%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
index 8e55695..24d5d2f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.systemui.utils
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userRestrictionChecker by Kosmos.Fixture { UserRestrictionChecker() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
index 838a273..3c63275 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -19,8 +19,14 @@
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeLocationController extends BaseLeakChecker<LocationChangeCallback>
         implements LocationController {
+
+    private final List<LocationChangeCallback> mCallbacks = new ArrayList<>();
+
     public FakeLocationController(LeakCheck test) {
         super(test, "location");
     }
@@ -37,6 +43,19 @@
 
     @Override
     public boolean setLocationEnabled(boolean enabled) {
+        mCallbacks.forEach(callback -> callback.onLocationSettingsChanged(enabled));
         return false;
     }
+
+    @Override
+    public void addCallback(LocationChangeCallback callback) {
+        super.addCallback(callback);
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public void removeCallback(LocationChangeCallback callback) {
+        super.removeCallback(callback);
+        mCallbacks.remove(callback);
+    }
 }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
similarity index 68%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
copy to packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
index 8e55695..a7a37b2 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
+++ b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-package com.android.settingslib.spa.screenshot.widget.button;
+package com.android.wm.shell.bubbles
 
-import org.robolectric.annotation.GraphicsMode;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import java.util.Optional
+
+var Kosmos.bubblesOptional by Kosmos.Fixture { Optional.of(bubbles) }
+var Kosmos.bubbles by Kosmos.Fixture { mock<Bubbles> {} }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
index a639df5..2bc2db3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.unfold
 
+import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.dagger.UseReceivingFilter
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
+import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
 import dagger.Module
 import dagger.Provides
@@ -33,16 +36,25 @@
     @Singleton
     fun provideTransitionProvider(
         config: UnfoldTransitionConfig,
-        traceListener: ATraceLoggerTransitionProgressListener,
+        traceListener: ATraceLoggerTransitionProgressListener.Factory,
         remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>,
     ): Optional<RemoteUnfoldTransitionReceiver> {
         if (!config.isEnabled) {
             return Optional.empty()
         }
         val remoteReceiver = remoteReceiverProvider.get()
-        remoteReceiver.addCallback(traceListener)
+        remoteReceiver.addCallback(traceListener.create("remoteReceiver"))
         return Optional.of(remoteReceiver)
     }
 
     @Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true
+
+    @Provides
+    @UnfoldMain
+    fun provideMainRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldMain mainHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(mainHandler)
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index c3a6cf0..31b7ccc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -22,12 +22,12 @@
 import android.hardware.display.DisplayManager
 import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.RotationChangeProvider
-import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -63,13 +63,12 @@
             @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
             @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
             @BindsInstance displayManager: DisplayManager,
-            @BindsInstance contentResolver: ContentResolver = context.contentResolver
+            @BindsInstance @UnfoldBg bgHandler: Handler,
+            @BindsInstance contentResolver: ContentResolver = context.contentResolver,
         ): UnfoldSharedComponent
     }
 
     val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
-    val hingeAngleProvider: HingeAngleProvider
-    val rotationChangeProvider: RotationChangeProvider
 }
 
 /**
@@ -94,7 +93,8 @@
     }
 
     val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver>
-    val rotationChangeProvider: RotationChangeProvider
+
+    @UnfoldMain fun getRotationChangeProvider(): RotationChangeProvider
 }
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 7473ca6..42d31b3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.unfold
 
+import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
@@ -24,6 +27,7 @@
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateRepository
 import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
+import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -38,16 +42,18 @@
 import javax.inject.Provider
 import javax.inject.Singleton
 
-@Module(includes = [UnfoldSharedInternalModule::class])
+@Module(
+    includes =
+        [
+            UnfoldSharedInternalModule::class,
+            UnfoldRotationProviderInternalModule::class,
+            HingeAngleProviderInternalModule::class,
+            FoldStateProviderModule::class,
+        ]
+)
 class UnfoldSharedModule {
     @Provides
     @Singleton
-    fun provideFoldStateProvider(
-        deviceFoldStateProvider: DeviceFoldStateProvider
-    ): FoldStateProvider = deviceFoldStateProvider
-
-    @Provides
-    @Singleton
     fun unfoldKeyguardVisibilityProvider(
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityProvider = impl
@@ -60,9 +66,7 @@
 
     @Provides
     @Singleton
-    fun foldStateRepository(
-            impl: FoldStateRepositoryImpl
-    ): FoldStateRepository = impl
+    fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl
 }
 
 /**
@@ -77,17 +81,69 @@
     fun unfoldTransitionProgressProvider(
         config: UnfoldTransitionConfig,
         scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+        tracingListener: ATraceLoggerTransitionProgressListener.Factory,
+        physicsBasedUnfoldTransitionProgressProvider:
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
+        fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        foldStateProvider: FoldStateProvider,
+        @UnfoldMain mainHandler: Handler,
+    ): Optional<UnfoldTransitionProgressProvider> {
+        return createOptionalUnfoldTransitionProgressProvider(
+            config = config,
+            scaleAwareProviderFactory = scaleAwareProviderFactory,
+            tracingListener = tracingListener.create("MainThread"),
+            physicsBasedUnfoldTransitionProgressProvider =
+                physicsBasedUnfoldTransitionProgressProvider,
+            fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
+            foldStateProvider = foldStateProvider,
+            progressHandler = mainHandler,
+        )
+    }
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun unfoldBgTransitionProgressProvider(
+        config: UnfoldTransitionConfig,
+        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+        tracingListener: ATraceLoggerTransitionProgressListener.Factory,
+        physicsBasedUnfoldTransitionProgressProvider:
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
+        fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        @UnfoldBg bgFoldStateProvider: FoldStateProvider,
+        @UnfoldBg bgHandler: Handler,
+    ): Optional<UnfoldTransitionProgressProvider> {
+        return createOptionalUnfoldTransitionProgressProvider(
+            config = config,
+            scaleAwareProviderFactory = scaleAwareProviderFactory,
+            tracingListener = tracingListener.create("BgThread"),
+            physicsBasedUnfoldTransitionProgressProvider =
+                physicsBasedUnfoldTransitionProgressProvider,
+            fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
+            foldStateProvider = bgFoldStateProvider,
+            progressHandler = bgHandler,
+        )
+    }
+
+    private fun createOptionalUnfoldTransitionProgressProvider(
+        config: UnfoldTransitionConfig,
+        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
         tracingListener: ATraceLoggerTransitionProgressListener,
         physicsBasedUnfoldTransitionProgressProvider:
-            Provider<PhysicsBasedUnfoldTransitionProgressProvider>,
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
         fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        foldStateProvider: FoldStateProvider,
+        progressHandler: Handler,
     ): Optional<UnfoldTransitionProgressProvider> {
         if (!config.isEnabled) {
             return Optional.empty()
         }
         val baseProgressProvider =
             if (config.isHingeAngleEnabled) {
-                physicsBasedUnfoldTransitionProgressProvider.get()
+                physicsBasedUnfoldTransitionProgressProvider.create(
+                    foldStateProvider,
+                    progressHandler
+                )
             } else {
                 fixedTimingTransitionProgressProvider.get()
             }
@@ -101,26 +157,105 @@
     }
 
     @Provides
+    @Singleton
+    fun provideProgressForwarder(
+        config: UnfoldTransitionConfig,
+        progressForwarder: Provider<UnfoldTransitionProgressForwarder>
+    ): Optional<UnfoldTransitionProgressForwarder> {
+        if (!config.isEnabled) {
+            return Optional.empty()
+        }
+        return Optional.of(progressForwarder.get())
+    }
+}
+
+/**
+ * Provides [FoldStateProvider]. The [UnfoldBg] annotated binding sends progress in the [UnfoldBg]
+ * handler.
+ */
+@Module
+internal class FoldStateProviderModule {
+    @Provides
+    @Singleton
+    fun provideFoldStateProvider(
+        factory: DeviceFoldStateProvider.Factory,
+        @UnfoldMain hingeAngleProvider: HingeAngleProvider,
+        @UnfoldMain rotationChangeProvider: RotationChangeProvider,
+        @UnfoldMain mainHandler: Handler,
+    ): FoldStateProvider =
+        factory.create(
+            hingeAngleProvider,
+            rotationChangeProvider,
+            progressHandler = mainHandler
+        )
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun provideBgFoldStateProvider(
+        factory: DeviceFoldStateProvider.Factory,
+        @UnfoldBg hingeAngleProvider: HingeAngleProvider,
+        @UnfoldBg rotationChangeProvider: RotationChangeProvider,
+        @UnfoldBg bgHandler: Handler,
+    ): FoldStateProvider =
+        factory.create(
+            hingeAngleProvider,
+            rotationChangeProvider,
+            progressHandler = bgHandler
+        )
+}
+
+/** Provides bindings for both [UnfoldMain] and [UnfoldBg] [HingeAngleProvider]. */
+@Module
+internal class HingeAngleProviderInternalModule {
+    @Provides
+    @UnfoldMain
     fun hingeAngleProvider(
         config: UnfoldTransitionConfig,
-        hingeAngleSensorProvider: Provider<HingeSensorAngleProvider>
+        @UnfoldMain handler: Handler,
+        hingeAngleSensorProvider: HingeSensorAngleProvider.Factory
     ): HingeAngleProvider {
         return if (config.isHingeAngleEnabled) {
-            hingeAngleSensorProvider.get()
+            hingeAngleSensorProvider.create(handler)
         } else {
             EmptyHingeAngleProvider
         }
     }
 
     @Provides
-    @Singleton
-    fun provideProgressForwarder(
-            config: UnfoldTransitionConfig,
-            progressForwarder: Provider<UnfoldTransitionProgressForwarder>
-    ): Optional<UnfoldTransitionProgressForwarder> {
-        if (!config.isEnabled) {
-            return Optional.empty()
+    @UnfoldBg
+    fun hingeAngleProviderBg(
+        config: UnfoldTransitionConfig,
+        @UnfoldBg handler: Handler,
+        hingeAngleSensorProvider: HingeSensorAngleProvider.Factory
+    ): HingeAngleProvider {
+        return if (config.isHingeAngleEnabled) {
+            hingeAngleSensorProvider.create(handler)
+        } else {
+            EmptyHingeAngleProvider
         }
-        return Optional.of(progressForwarder.get())
+    }
+}
+
+@Module
+internal class UnfoldRotationProviderInternalModule {
+    @Provides
+    @Singleton
+    @UnfoldMain
+    fun provideRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldMain mainHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(mainHandler)
+    }
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun provideBgRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldBg bgHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(bgHandler)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 1839919..1cbaf31 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -48,6 +48,7 @@
         singleThreadBgExecutor: Executor,
         tracingTagPrefix: String,
         displayManager: DisplayManager,
+        bgHandler: Handler,
 ): UnfoldSharedComponent =
         DaggerUnfoldSharedComponent.factory()
                 .create(
@@ -62,6 +63,7 @@
                         singleThreadBgExecutor,
                         tracingTagPrefix,
                         displayManager,
+                        bgHandler,
                 )
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt
new file mode 100644
index 0000000..7cd4419
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.dagger
+
+import javax.inject.Qualifier
+
+/** Annotation for background computations related to unfold lib. */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBg
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index f8f168b..907bf46 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -20,6 +20,7 @@
 import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
 import android.content.Context
+import android.os.Handler
 import android.os.Trace
 import android.util.FloatProperty
 import android.util.Log
@@ -38,13 +39,25 @@
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
 import com.android.systemui.unfold.updates.name
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
-/** Maps fold updates to unfold transition progress using DynamicAnimation. */
+/**
+ * Maps fold updates to unfold transition progress using DynamicAnimation.
+ *
+ * Note that all variable accesses must be done in the [Handler] provided in the constructor, that
+ * might be different than [mainHandler]. When a custom handler is provided, the [SpringAnimation]
+ * uses a scheduler different than the default one.
+ */
 class PhysicsBasedUnfoldTransitionProgressProvider
-@Inject
-constructor(context: Context, private val foldStateProvider: FoldStateProvider) :
-    UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
+@AssistedInject
+constructor(
+    context: Context,
+    private val schedulerFactory: UnfoldFrameCallbackScheduler.Factory,
+    @Assisted private val foldStateProvider: FoldStateProvider,
+    @Assisted private val progressHandler: Handler,
+) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
 
     private val emphasizedInterpolator =
         loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in)
@@ -63,6 +76,7 @@
 
     private var transitionProgress: Float = 0.0f
         set(value) {
+            assertInProgressThread()
             if (isTransitionRunning) {
                 listeners.forEach { it.onTransitionProgress(value) }
             }
@@ -72,8 +86,14 @@
     private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
 
     init {
-        foldStateProvider.addCallback(this)
-        foldStateProvider.start()
+        progressHandler.post {
+            // The scheduler needs to be created in the progress handler in order to get the correct
+            // choreographer and frame callbacks. This is because the choreographer can be get only
+            // as a thread local.
+            springAnimation.scheduler = schedulerFactory.create()
+            foldStateProvider.addCallback(this)
+            foldStateProvider.start()
+        }
     }
 
     override fun destroy() {
@@ -81,6 +101,8 @@
     }
 
     override fun onHingeAngleUpdate(angle: Float) {
+        assertInProgressThread()
+
         if (!isTransitionRunning || isAnimatedCancelRunning) return
         val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION)
         springAnimation.animateToFinalPosition(progress)
@@ -90,6 +112,7 @@
         if (amount < low) low else if (amount > high) high else amount
 
     override fun onFoldUpdate(@FoldUpdate update: Int) {
+        assertInProgressThread()
         when (update) {
             FOLD_UPDATE_FINISH_FULL_OPEN,
             FOLD_UPDATE_FINISH_HALF_OPEN -> {
@@ -148,6 +171,7 @@
     }
 
     private fun cancelTransition(endValue: Float, animate: Boolean) {
+        assertInProgressThread()
         if (isTransitionRunning && animate) {
             if (endValue == 1.0f && !isAnimatedCancelRunning) {
                 listeners.forEach { it.onTransitionFinishing() }
@@ -165,7 +189,6 @@
             isAnimatedCancelRunning = false
             isTransitionRunning = false
             springAnimation.cancel()
-
             cannedAnimator?.removeAllListeners()
             cannedAnimator?.cancel()
             cannedAnimator = null
@@ -182,7 +205,7 @@
         animation: DynamicAnimation<out DynamicAnimation<*>>,
         canceled: Boolean,
         value: Float,
-        velocity: Float
+        velocity: Float,
     ) {
         if (isAnimatedCancelRunning) {
             cancelTransition(value, animate = false)
@@ -202,6 +225,7 @@
     }
 
     private fun startTransition(startValue: Float) {
+        assertInProgressThread()
         if (!isTransitionRunning) onStartTransition()
 
         springAnimation.apply {
@@ -221,14 +245,16 @@
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
-        listeners.add(listener)
+        progressHandler.post { listeners.add(listener) }
     }
 
     override fun removeCallback(listener: TransitionProgressListener) {
-        listeners.remove(listener)
+        progressHandler.post { listeners.remove(listener) }
     }
 
     private fun startCannedCancelAnimation() {
+        assertInProgressThread()
+
         cannedAnimator?.cancel()
         cannedAnimator = null
 
@@ -264,7 +290,7 @@
 
         override fun setValue(
             provider: PhysicsBasedUnfoldTransitionProgressProvider,
-            value: Float
+            value: Float,
         ) {
             provider.transitionProgress = value
         }
@@ -272,6 +298,25 @@
         override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
             provider.transitionProgress
     }
+
+    private fun assertInProgressThread() {
+        check(progressHandler.looper.isCurrentThread) {
+            val progressThread = progressHandler.looper.thread
+            val thisThread = Thread.currentThread()
+            """should be called from the progress thread.
+                progressThread=$progressThread tid=${progressThread.id}
+                Thread.currentThread()=$thisThread tid=${thisThread.id}"""
+                .trimMargin()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            foldStateProvider: FoldStateProvider,
+            handler: Handler,
+        ): PhysicsBasedUnfoldTransitionProgressProvider
+    }
 }
 
 private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt
new file mode 100644
index 0000000..1dffd84
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.progress
+
+import android.os.Looper
+import android.view.Choreographer
+import androidx.dynamicanimation.animation.FrameCallbackScheduler
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Scheduler that posts animation progresses on a thread different than the ui one.
+ *
+ * The following is taken from [AnimationHandler.FrameCallbackScheduler16]. It is extracted here as
+ * there are no guarantees which implementation the [DynamicAnimation] class would use otherwise.
+ * This allows classes using [DynamicAnimation] to be created in any thread, but still use the
+ * scheduler for a specific thread.
+ *
+ * Technically the [AssistedInject] is not needed: it's just to have a nicer factory with a
+ * documentation snippet instead of using a plain dagger provider.
+ */
+class UnfoldFrameCallbackScheduler @AssistedInject constructor() : FrameCallbackScheduler {
+
+    private val choreographer = Choreographer.getInstance()
+    private val looper =
+        Looper.myLooper() ?: error("This should be created in a thread with a looper.")
+
+    override fun postFrameCallback(frameCallback: Runnable) {
+        choreographer.postFrameCallback { frameCallback.run() }
+    }
+
+    override fun isCurrentThread(): Boolean {
+        return looper.isCurrentThread
+    }
+
+    @AssistedFactory
+    interface Factory {
+        /**
+         * Creates a [FrameCallbackScheduler] that uses [Choreographer] to post frame callbacks.
+         *
+         * Note that the choreographer used depends on the thread this [create] is called on, as it
+         * is get from a thread static attribute.
+         */
+        fun create(): UnfoldFrameCallbackScheduler
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 003013e..77f637b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -23,37 +23,34 @@
 import androidx.core.util.Consumer
 import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldMain
-import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
-import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
-import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
 import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
 import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
-import javax.inject.Inject
 
 class DeviceFoldStateProvider
-@Inject
+@AssistedInject
 constructor(
     config: UnfoldTransitionConfig,
-    private val hingeAngleProvider: HingeAngleProvider,
+    private val context: Context,
     private val screenStatusProvider: ScreenStatusProvider,
-    private val foldProvider: FoldProvider,
     private val activityTypeProvider: CurrentActivityTypeProvider,
     private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider,
-    private val rotationChangeProvider: RotationChangeProvider,
-    private val context: Context,
-    @UnfoldMain private val mainExecutor: Executor,
-    @UnfoldMain private val handler: Handler
+    private val foldProvider: FoldProvider,
+    @Assisted private val hingeAngleProvider: HingeAngleProvider,
+    @Assisted private val rotationChangeProvider: RotationChangeProvider,
+    @Assisted private val progressHandler: Handler,
 ) : FoldStateProvider {
+    private val outputListeners = CopyOnWriteArrayList<FoldStateProvider.FoldUpdatesListener>()
 
-    private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
-
-    @FoldUpdate private var lastFoldUpdate: Int? = null
+    @FoldStateProvider.FoldUpdate private var lastFoldUpdate: Int? = null
 
     @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
     @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f
@@ -61,11 +58,9 @@
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
     private val foldStateListener = FoldStateListener()
-    private val mainLooper = handler.looper
     private val timeoutRunnable = Runnable { cancelAnimation() }
-    private val rotationListener = RotationListener {
-        if (isTransitionInProgress) cancelAnimation()
-    }
+    private val rotationListener = FoldRotationListener()
+    private val progressExecutor = Executor { progressHandler.post(it) }
 
     /**
      * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
@@ -80,9 +75,9 @@
     private var isStarted = false
 
     override fun start() {
-        assertMainThread()
         if (isStarted) return
-        foldProvider.registerCallback(foldStateListener, mainExecutor)
+        foldProvider.registerCallback(foldStateListener, progressExecutor)
+        // TODO(b/277879146): get callbacks in the background
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
         rotationChangeProvider.addCallback(rotationListener)
@@ -91,7 +86,6 @@
     }
 
     override fun stop() {
-        assertMainThread()
         screenStatusProvider.removeCallback(screenListener)
         foldProvider.unregisterCallback(foldStateListener)
         hingeAngleProvider.removeCallback(hingeAngleListener)
@@ -101,11 +95,11 @@
         isStarted = false
     }
 
-    override fun addCallback(listener: FoldUpdatesListener) {
+    override fun addCallback(listener: FoldStateProvider.FoldUpdatesListener) {
         outputListeners.add(listener)
     }
 
-    override fun removeCallback(listener: FoldUpdatesListener) {
+    override fun removeCallback(listener: FoldStateProvider.FoldUpdatesListener) {
         outputListeners.remove(listener)
     }
 
@@ -121,6 +115,7 @@
                 lastFoldUpdate == FOLD_UPDATE_START_CLOSING
 
     private fun onHingeAngle(angle: Float) {
+        assertInProgressThread()
         if (DEBUG) {
             Log.d(
                 TAG,
@@ -131,14 +126,14 @@
         }
 
         val currentDirection =
-                if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+            if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
         if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
             lastHingeAngleBeforeTransition = lastHingeAngle
         }
 
         val isClosing = angle < lastHingeAngleBeforeTransition
         val transitionUpdate =
-                if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+            if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
         val angleChangeSurpassedThreshold =
             Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
@@ -150,12 +145,12 @@
             angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
                 eventNotAlreadyDispatched && // we haven't sent transition event already
                 !isFullyOpened && // do not send transition event if we are in fully opened hinge
-                                  // angle range as closing threshold could overlap this range
+                // angle range as closing threshold could overlap this range
                 screenAvailableEventSent && // do not send transition event if we are still in the
-                                            // process of turning on the inner display
+                // process of turning on the inner display
                 isClosingThresholdMet(angle) && // hinge angle is below certain threshold.
                 isOnLargeScreen // Avoids sending closing event when on small screen.
-                                // Start event is sent regardless due to hall sensor.
+        // Start event is sent regardless due to hall sensor.
         ) {
             notifyFoldUpdate(transitionUpdate, lastHingeAngle)
         }
@@ -202,6 +197,7 @@
 
     private inner class FoldStateListener : FoldProvider.FoldCallback {
         override fun onFoldUpdated(isFolded: Boolean) {
+            assertInProgressThread()
             this@DeviceFoldStateProvider.isFolded = isFolded
             lastHingeAngle = FULLY_CLOSED_DEGREES
 
@@ -218,7 +214,14 @@
         }
     }
 
-    private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) {
+    private inner class FoldRotationListener : RotationChangeProvider.RotationListener {
+        override fun onRotationChanged(newRotation: Int) {
+            assertInProgressThread()
+            if (isTransitionInProgress) cancelAnimation()
+        }
+    }
+
+    private fun notifyFoldUpdate(@FoldStateProvider.FoldUpdate update: Int, angle: Float) {
         if (DEBUG) {
             Log.d(TAG, update.name())
         }
@@ -236,11 +239,11 @@
         if (isTransitionInProgress) {
             cancelTimeout()
         }
-        handler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong())
+        progressHandler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong())
     }
 
     private fun cancelTimeout() {
-        handler.removeCallbacks(timeoutRunnable)
+        progressHandler.removeCallbacks(timeoutRunnable)
     }
 
     private fun cancelAnimation(): Unit =
@@ -249,42 +252,61 @@
     private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
 
         override fun onScreenTurnedOn() {
-            // Trigger this event only if we are unfolded and this is the first screen
-            // turned on event since unfold started. This prevents running the animation when
-            // turning on the internal display using the power button.
-            // Initially isUnfoldHandled is true so it will be reset to false *only* when we
-            // receive 'folded' event. If SystemUI started when device is already folded it will
-            // still receive 'folded' event on startup.
-            if (!isFolded && !isUnfoldHandled) {
-                outputListeners.forEach { it.onUnfoldedScreenAvailable() }
-                isUnfoldHandled = true
+            executeInProgressThread {
+                // Trigger this event only if we are unfolded and this is the first screen
+                // turned on event since unfold started. This prevents running the animation when
+                // turning on the internal display using the power button.
+                // Initially isUnfoldHandled is true so it will be reset to false *only* when we
+                // receive 'folded' event. If SystemUI started when device is already folded it will
+                // still receive 'folded' event on startup.
+                if (!isFolded && !isUnfoldHandled) {
+                    outputListeners.forEach { it.onUnfoldedScreenAvailable() }
+                    isUnfoldHandled = true
+                }
             }
         }
 
         override fun markScreenAsTurnedOn() {
-            if (!isFolded) {
-                isUnfoldHandled = true
+            executeInProgressThread {
+                if (!isFolded) {
+                    isUnfoldHandled = true
+                }
             }
         }
 
         override fun onScreenTurningOn() {
-            isScreenOn = true
-            updateHingeAngleProviderState()
+            executeInProgressThread {
+                isScreenOn = true
+                updateHingeAngleProviderState()
+            }
         }
 
         override fun onScreenTurningOff() {
-            isScreenOn = false
-            updateHingeAngleProviderState()
+            executeInProgressThread {
+                isScreenOn = false
+                updateHingeAngleProviderState()
+            }
+        }
+
+        /**
+         * Needed just for compatibility while not all data sources are providing data in the
+         * background.
+         *
+         * TODO(b/277879146): Remove once ScreeStatusProvider provides in the background.
+         */
+        private fun executeInProgressThread(f: () -> Unit) {
+            progressHandler.post { f() }
         }
     }
 
     private fun isOnLargeScreen(): Boolean {
-      return context.resources.configuration.smallestScreenWidthDp >
-          INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+        return context.resources.configuration.smallestScreenWidthDp >
+            INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
     }
 
     /** While the screen is off or the device is folded, hinge angle updates are not needed. */
     private fun updateHingeAngleProviderState() {
+        assertInProgressThread()
         if (isScreenOn && !isFolded) {
             hingeAngleProvider.start()
         } else {
@@ -294,20 +316,34 @@
 
     private inner class HingeAngleListener : Consumer<Float> {
         override fun accept(angle: Float) {
+            assertInProgressThread()
             onHingeAngle(angle)
         }
     }
 
-    private fun assertMainThread() {
-        check(mainLooper.isCurrentThread) {
-            ("should be called from the main thread." +
-                    " sMainLooper.threadName=" + mainLooper.thread.name +
-                    " Thread.currentThread()=" + Thread.currentThread().name)
+    private fun assertInProgressThread() {
+        check(progressHandler.looper.isCurrentThread) {
+            val progressThread = progressHandler.looper.thread
+            val thisThread = Thread.currentThread()
+            """should be called from the progress thread.
+                progressThread=$progressThread tid=${progressThread.id}
+                Thread.currentThread()=$thisThread tid=${thisThread.id}"""
+                .trimMargin()
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates a [DeviceFoldStateProvider] using the provided dependencies. */
+        fun create(
+            hingeAngleProvider: HingeAngleProvider,
+            rotationChangeProvider: RotationChangeProvider,
+            progressHandler: Handler,
+        ): DeviceFoldStateProvider
+    }
 }
 
-fun @receiver:FoldUpdate Int.name() =
+fun @receiver:FoldStateProvider.FoldUpdate Int.name() =
     when (this) {
         FOLD_UPDATE_START_OPENING -> "START_OPENING"
         FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index ce8f1a1..82ea362 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -20,20 +20,21 @@
 import android.hardware.display.DisplayManager
 import android.os.Handler
 import android.os.RemoteException
-import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.util.CallbackController
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
 /**
- * Allows to subscribe to rotation changes. Updates are provided for the display associated
- * to [context].
+ * Allows to subscribe to rotation changes. Updates are provided for the display associated to
+ * [context].
  */
 class RotationChangeProvider
-@Inject
+@AssistedInject
 constructor(
     private val displayManager: DisplayManager,
     private val context: Context,
-    @UnfoldMain private val mainHandler: Handler,
+    @Assisted private val handler: Handler,
 ) : CallbackController<RotationChangeProvider.RotationListener> {
 
     private val listeners = mutableListOf<RotationListener>()
@@ -42,7 +43,7 @@
     private var lastRotation: Int? = null
 
     override fun addCallback(listener: RotationListener) {
-        mainHandler.post {
+        handler.post {
             if (listeners.isEmpty()) {
                 subscribeToRotation()
             }
@@ -51,7 +52,7 @@
     }
 
     override fun removeCallback(listener: RotationListener) {
-        mainHandler.post {
+        handler.post {
             listeners -= listener
             if (listeners.isEmpty()) {
                 unsubscribeToRotation()
@@ -62,7 +63,7 @@
 
     private fun subscribeToRotation() {
         try {
-            displayManager.registerDisplayListener(displayListener, mainHandler)
+            displayManager.registerDisplayListener(displayListener, handler)
         } catch (e: RemoteException) {
             throw e.rethrowFromSystemServer()
         }
@@ -100,4 +101,10 @@
 
         override fun onDisplayRemoved(displayId: Int) {}
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */
+        fun create(handler: Handler): RotationChangeProvider
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 89fb12e..14c4cc0 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -18,21 +18,26 @@
 import android.hardware.SensorEvent
 import android.hardware.SensorEventListener
 import android.hardware.SensorManager
+import android.os.Handler
 import android.os.Trace
 import androidx.core.util.Consumer
 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
-import javax.inject.Inject
 
 internal class HingeSensorAngleProvider
-@Inject
+@AssistedInject
 constructor(
     private val sensorManager: SensorManager,
-    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor
+    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
+    @Assisted private val listenerHandler: Handler,
 ) : HingeAngleProvider {
 
     private val sensorListener = HingeAngleSensorListener()
-    private val listeners: MutableList<Consumer<Float>> = arrayListOf()
+    private val listeners: MutableList<Consumer<Float>> = CopyOnWriteArrayList()
     var started = false
 
     override fun start() {
@@ -43,7 +48,8 @@
             sensorManager.registerListener(
                 sensorListener,
                 sensor,
-                SensorManager.SENSOR_DELAY_FASTEST
+                SensorManager.SENSOR_DELAY_FASTEST,
+                listenerHandler
             )
             Trace.endSection()
 
@@ -75,4 +81,10 @@
             listeners.forEach { it.accept(event.values[0]) }
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates an [HingeSensorAngleProvider] that sends updates using [handler]. */
+        fun create(handler: Handler): HingeSensorAngleProvider
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index d8bc018..a31896a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -16,7 +16,9 @@
 
 import android.os.Trace
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import javax.inject.Qualifier
 
 /**
@@ -26,11 +28,11 @@
  * for each fold/unfold: in (1) systemui and (2) launcher process.
  */
 class ATraceLoggerTransitionProgressListener
-@Inject
-internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) :
+@AssistedInject
+internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String, @Assisted details: String) :
     TransitionProgressListener {
 
-    private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME"
+    private val traceName = "$tracePrefix$details#$UNFOLD_TRANSITION_TRACE_NAME"
 
     override fun onTransitionStarted() {
         Trace.beginAsyncSection(traceName, /* cookie= */ 0)
@@ -43,6 +45,12 @@
     override fun onTransitionProgress(progress: Float) {
         Trace.setCounter(traceName, (progress * 100).toLong())
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates an [ATraceLoggerTransitionProgressListener] with [details] in the track name. */
+        fun create(details: String): ATraceLoggerTransitionProgressListener
+    }
 }
 
 private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress"
diff --git a/packages/VpnDialogs/res/values-da/strings.xml b/packages/VpnDialogs/res/values-da/strings.xml
index 63a32f9..2d55a06 100644
--- a/packages/VpnDialogs/res/values-da/strings.xml
+++ b/packages/VpnDialogs/res/values-da/strings.xml
@@ -31,7 +31,7 @@
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Skift VPN-indstillinger"</string>
     <string name="configure" msgid="4905518375574791375">"Konfigurer"</string>
-    <string name="disconnect" msgid="971412338304200056">"Fjern tilknytning"</string>
+    <string name="disconnect" msgid="971412338304200056">"Afbryd forbindelse"</string>
     <string name="open_app" msgid="3717639178595958667">"Åbn app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Luk"</string>
     <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index b9e34ee..e013a3e 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -25,12 +25,33 @@
 }
 
 java_library {
-    name: "ravenwood-junit",
-    srcs: ["junit-src/**/*.java"],
+    name: "ravenwood-junit-impl",
+    srcs: [
+        "junit-src/**/*.java",
+        "junit-impl-src/**/*.java",
+    ],
     libs: [
         "framework-minus-apex.ravenwood",
         "junit",
     ],
+    sdk_version: "core_current",
+    visibility: ["//frameworks/base"],
+}
+
+// Carefully compiles against only test_current to support tests that
+// want to verify they're unbundled.  The "impl" library above is what
+// ships inside the Ravenwood environment to actually drive any API
+// access to implementation details.
+java_library {
+    name: "ravenwood-junit",
+    srcs: [
+        "junit-src/**/*.java",
+        "junit-stub-src/**/*.java",
+    ],
+    sdk_version: "test_current",
+    libs: [
+        "junit",
+    ],
     visibility: ["//visibility:public"],
 }
 
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
index 1d31579..f02f06c 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
@@ -18,7 +18,6 @@
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -32,7 +31,7 @@
  *
  * @hide
  */
-@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Target({FIELD, METHOD, CONSTRUCTOR})
 @Retention(RetentionPolicy.CLASS)
 public @interface RavenwoodKeep {
 }
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java
new file mode 100644
index 0000000..7847274
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java
@@ -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 android.ravenwood.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ *
+ * @hide
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeepPartialClass {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java
new file mode 100644
index 0000000..eeebee9
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java
@@ -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 android.ravenwood.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * @hide
+ */
+@Target(TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeepStaticInitializer {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
index a234a9b..0bb1f39 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
@@ -34,4 +34,13 @@
 @Target({METHOD, CONSTRUCTOR})
 @Retention(RetentionPolicy.CLASS)
 public @interface RavenwoodThrow {
+    /**
+     * One or more classes that aren't yet supported by Ravenwood, which is why this method throws.
+     */
+    Class<?>[] blockedBy() default {};
+
+    /**
+     * General free-form description of why this method throws.
+     */
+    String reason() default "";
 }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index aa2d470..c70c171 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -17,12 +17,14 @@
 class android.util.Log stubclass
 class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host
 class android.util.LogPrinter stubclass
+class android.util.LocalLog stubclass
 
 # String Manipulation
 class android.util.Printer stubclass
 class android.util.PrintStreamPrinter stubclass
 class android.util.PrintWriterPrinter stubclass
 class android.util.StringBuilderPrinter stubclass
+class android.util.IndentingPrintWriter stubclass
 
 # Properties
 class android.util.Property stubclass
@@ -76,6 +78,8 @@
 class android.util.UtilConfig stubclass
 
 # Internals
+class com.android.internal.util.FastMath stubclass
+class com.android.internal.util.FastPrintWriter stubclass
 class com.android.internal.util.GrowingArrayUtils stubclass
 class com.android.internal.util.LineBreakBufferedWriter stubclass
 class com.android.internal.util.Preconditions stubclass
@@ -106,6 +110,7 @@
 class android.os.PersistableBundle stubclass
 
 # Misc
+class android.os.HandlerExecutor stubclass
 class android.os.PatternMatcher stubclass
 class android.os.ParcelUuid stubclass
 
@@ -129,7 +134,3 @@
 # Context: just enough to support wrapper, no further functionality
 class android.content.Context stub
     method <init> ()V stub
-
-# Text
-class android.text.TextUtils stub
-    method isEmpty (Ljava/lang/CharSequence;)Z stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
new file mode 100644
index 0000000..be0c09e
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.os.HandlerThread;
+import android.os.Looper;
+
+import java.util.Objects;
+
+public class RavenwoodRuleImpl {
+    private static final String MAIN_THREAD_NAME = "RavenwoodMain";
+
+    public static boolean isUnderRavenwood() {
+        return true;
+    }
+
+    public static void init(RavenwoodRule rule) {
+        android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
+        android.os.Binder.init$ravenwood();
+
+        if (rule.mProvideMainThread) {
+            final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
+            main.start();
+            Looper.setMainLooperForTest(main.getLooper());
+        }
+    }
+
+    public static void reset(RavenwoodRule rule) {
+        if (rule.mProvideMainThread) {
+            Looper.getMainLooper().quit();
+            Looper.clearMainLooperForTest();
+        }
+
+        android.os.Process.reset$ravenwood();
+        android.os.Binder.reset$ravenwood();
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
index 0aac084..edb0442 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
@@ -22,12 +22,30 @@
 import java.lang.annotation.Target;
 
 /**
- * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
+ * Test methods marked with this annotation are quietly ignored when running under a Ravenwood test
+ * environment. The test continues to execute normally under all other non-Ravenwood test
+ * environments.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * Developers are encouraged to use either the {@code blockedBy} and/or {@code reason} arguments
+ * to document why a test is being ignored, to aid in future audits of tests that are candidates
+ * to be enabled.
  *
  * @hide
  */
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface IgnoreUnderRavenwood {
+    /**
+     * One or more classes that aren't yet supported by Ravenwood, which this test depends on.
+     */
+    Class<?>[] blockedBy() default {};
+
+    /**
+     * General free-form description of why this test is being ignored.
+     */
+    String reason() default "";
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index bffd0cd..9db5b98 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,7 +16,6 @@
 
 package android.platform.test.ravenwood;
 
-import android.os.Process;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -35,12 +34,20 @@
 public class RavenwoodRule implements TestRule {
     private static AtomicInteger sNextPid = new AtomicInteger(100);
 
+    private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
+
+    private static final int SYSTEM_UID = 1000;
+    private static final int NOBODY_UID = 9999;
+    private static final int FIRST_APPLICATION_UID = 10000;
+
     /**
      * Unless the test author requests differently, run as "nobody", and give each collection of
      * tests its own unique PID.
      */
-    private int mUid = android.os.Process.NOBODY_UID;
-    private int mPid = sNextPid.getAndIncrement();
+    int mUid = NOBODY_UID;
+    int mPid = sNextPid.getAndIncrement();
+
+    boolean mProvideMainThread = false;
 
     public RavenwoodRule() {
     }
@@ -56,7 +63,7 @@
          * test. Has no effect under non-Ravenwood environments.
          */
         public Builder setProcessSystem() {
-            mRule.mUid = android.os.Process.SYSTEM_UID;
+            mRule.mUid = SYSTEM_UID;
             return this;
         }
 
@@ -65,7 +72,16 @@
          * test. Has no effect under non-Ravenwood environments.
          */
         public Builder setProcessApp() {
-            mRule.mUid = android.os.Process.FIRST_APPLICATION_UID;
+            mRule.mUid = FIRST_APPLICATION_UID;
+            return this;
+        }
+
+        /**
+         * Configure a "main" thread to be available for the duration of the test, as defined
+         * by {@code Looper.getMainLooper()}. Has no effect under non-Ravenwood environments.
+         */
+        public Builder setProvideMainThread(boolean provideMainThread) {
+            mRule.mProvideMainThread = provideMainThread;
             return this;
         }
 
@@ -78,18 +94,7 @@
      * Return if the current process is running under a Ravenwood test environment.
      */
     public boolean isUnderRavenwood() {
-        // TODO: give ourselves a better environment signal
-        return System.getProperty("java.class.path").contains("ravenwood");
-    }
-
-    private void init() {
-        android.os.Process.init$ravenwood(mUid, mPid);
-        android.os.Binder.init$ravenwood();
-    }
-
-    private void reset() {
-        android.os.Process.reset$ravenwood();
-        android.os.Binder.reset$ravenwood();
+        return IS_UNDER_RAVENWOOD;
     }
 
     @Override
@@ -97,18 +102,17 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                final boolean isUnderRavenwood = isUnderRavenwood();
                 if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                    Assume.assumeFalse(isUnderRavenwood);
+                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                 }
-                if (isUnderRavenwood) {
-                    init();
+                if (IS_UNDER_RAVENWOOD) {
+                    RavenwoodRuleImpl.init(RavenwoodRule.this);
                 }
                 try {
                     base.evaluate();
                 } finally {
-                    if (isUnderRavenwood) {
-                        reset();
+                    if (IS_UNDER_RAVENWOOD) {
+                        RavenwoodRuleImpl.reset(RavenwoodRule.this);
                     }
                 }
             }
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
new file mode 100644
index 0000000..fb71e9d
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -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 android.platform.test.ravenwood;
+
+public class RavenwoodRuleImpl {
+    public static boolean isUnderRavenwood() {
+        return false;
+    }
+
+    public static void init(RavenwoodRule rule) {
+        // Must be provided by impl to reference runtime internals
+        throw new UnsupportedOperationException();
+    }
+
+    public static void reset(RavenwoodRule rule) {
+        // Must be provided by impl to reference runtime internals
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 0290bbe..07c2cd7c 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,13 +2,26 @@
 
 com.android.internal.util.ArrayUtils
 
+android.util.DataUnit
+android.util.EventLog
+android.util.IntArray
+android.util.LongArray
+android.util.Slog
+android.util.TimeUtils
 android.util.Xml
 
 android.os.Binder
 android.os.Binder$IdentitySupplier
+android.os.Handler
+android.os.HandlerExecutor
+android.os.HandlerThread
 android.os.IBinder
+android.os.Looper
+android.os.Message
+android.os.MessageQueue
 android.os.Process
 android.os.SystemClock
+android.os.ThreadLocalWorkSource
 android.os.UserHandle
 
 android.content.ClipData
@@ -20,3 +33,29 @@
 android.content.Intent
 android.content.IntentFilter
 android.content.UriMatcher
+
+android.database.AbstractCursor
+android.database.CharArrayBuffer
+android.database.ContentObservable
+android.database.ContentObserver
+android.database.Cursor
+android.database.CursorIndexOutOfBoundsException
+android.database.CursorJoiner
+android.database.CursorWrapper
+android.database.DataSetObservable
+android.database.DataSetObserver
+android.database.MatrixCursor
+android.database.MatrixCursor$RowBuilder
+android.database.MergeCursor
+android.database.Observable
+
+android.text.TextUtils
+android.text.TextUtils$SimpleStringSplitter
+
+android.accounts.Account
+
+android.graphics.Insets
+android.graphics.Point
+android.graphics.PointF
+android.graphics.Rect
+android.graphics.RectF
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
index 4b07ef6..8ad21fa 100644
--- a/ravenwood/ravenwood-standard-options.txt
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -7,6 +7,7 @@
 
 # Uncomment below lines to enable each feature.
 # --enable-non-stub-method-check
+--no-non-stub-method-check
 
 #--default-method-call-hook
 #    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -18,6 +19,9 @@
 --keep-annotation
     android.ravenwood.annotation.RavenwoodKeep
 
+--keep-annotation
+    android.ravenwood.annotation.RavenwoodKeepPartialClass
+
 --keep-class-annotation
     android.ravenwood.annotation.RavenwoodKeepWholeClass
 
@@ -35,3 +39,6 @@
 
 --class-load-hook-annotation
     android.ravenwood.annotation.RavenwoodClassLoadHook
+
+--keep-static-initializer-annotation
+    android.ravenwood.annotation.RavenwoodKeepStaticInitializer
diff --git a/services/Android.bp b/services/Android.bp
index 02a7a78..5cb8ec6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -140,6 +140,7 @@
         ":services.voiceinteraction-sources",
         ":services.wallpapereffectsgeneration-sources",
         ":services.wifi-sources",
+        ":framework-pm-common-shared-srcs",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f73b00c..7187895 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -34,6 +34,8 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
+import static com.android.window.flags.Flags.deleteCaptureDisplay;
+
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -1440,42 +1442,86 @@
                     AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback);
             return;
         }
-
         final long identity = Binder.clearCallingIdentity();
-        try {
-            mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
-                final ScreenshotHardwareBuffer screenshotBuffer = LocalServices
-                        .getService(DisplayManagerInternal.class).userScreenshot(displayId);
-                if (screenshotBuffer != null) {
-                    sendScreenshotSuccess(screenshotBuffer, callback);
-                } else {
-                    sendScreenshotFailure(
-                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback);
-                }
-            }, null).recycleOnUse());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        if (deleteCaptureDisplay()) {
+            try {
+                ScreenCapture.ScreenCaptureListener screenCaptureListener = new
+                        ScreenCapture.ScreenCaptureListener(
+                        (screenshotBuffer, result) -> {
+                            if (screenshotBuffer != null && result == 0) {
+                                sendScreenshotSuccess(screenshotBuffer, callback);
+                            } else {
+                                sendScreenshotFailure(
+                                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+                                        callback);
+                            }
+                        }
+                );
+                mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener);
+            } catch (Exception e) {
+                sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+                        callback);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            try {
+                mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+                    final ScreenshotHardwareBuffer screenshotBuffer = LocalServices
+                            .getService(DisplayManagerInternal.class).userScreenshot(displayId);
+                    if (screenshotBuffer != null) {
+                        sendScreenshotSuccess(screenshotBuffer, callback);
+                    } else {
+                        sendScreenshotFailure(
+                                AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+                                callback);
+                    }
+                }, null).recycleOnUse());
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer,
             RemoteCallback callback) {
-        final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-        final ParcelableColorSpace colorSpace =
-                new ParcelableColorSpace(screenshotBuffer.getColorSpace());
+        if (deleteCaptureDisplay()) {
+            mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+                final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+                final ParcelableColorSpace colorSpace =
+                        new ParcelableColorSpace(screenshotBuffer.getColorSpace());
 
-        final Bundle payload = new Bundle();
-        payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
-                AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
-        payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
-                hardwareBuffer);
-        payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
-        payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
-                SystemClock.uptimeMillis());
+                final Bundle payload = new Bundle();
+                payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
+                        AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
+                payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
+                        hardwareBuffer);
+                payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
+                payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
+                        SystemClock.uptimeMillis());
 
-        // Send back the result.
-        callback.sendResult(payload);
-        hardwareBuffer.close();
+                // Send back the result.
+                callback.sendResult(payload);
+                hardwareBuffer.close();
+            }, null).recycleOnUse());
+        } else {
+            final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+            final ParcelableColorSpace colorSpace =
+                    new ParcelableColorSpace(screenshotBuffer.getColorSpace());
+
+            final Bundle payload = new Bundle();
+            payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
+                    AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
+                    hardwareBuffer);
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
+            payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
+                    SystemClock.uptimeMillis());
+
+            // Send back the result.
+            callback.sendResult(payload);
+            hardwareBuffer.close();
+        }
     }
 
     private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b5e8c84..4b10111 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5580,6 +5580,8 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS,
+                "injectInputEventToInputFilter");
         synchronized (mLock) {
             final long endMillis =
                     SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 45ca726..e3797c9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -69,6 +69,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.gestures.GestureUtils;
 
 /**
@@ -257,12 +258,21 @@
                 public void logMagnificationTripleTap(boolean enabled) {
                     AccessibilityStatsLogUtils.logMagnificationTripleTap(enabled);
                 }
+
+                @Override
+                public void logMagnificationTwoFingerTripleTap(boolean enabled) {
+                    AccessibilityStatsLogUtils.logMagnificationTwoFingerTripleTap(enabled);
+                }
             };
         }
 
         mDelegatingState = new DelegatingState();
-        mDetectingState = new DetectingState(context);
-        mViewportDraggingState = new ViewportDraggingState();
+        mDetectingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                ? new DetectingStateWithMultiFinger(context)
+                : new DetectingState(context);
+        mViewportDraggingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                ? new ViewportDraggingStateWithMultiFinger()
+                : new ViewportDraggingState();
         mPanningScalingState = new PanningScalingState(context);
         mSinglePanningState = new SinglePanningState(context);
         mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
@@ -414,6 +424,7 @@
     /** An interface that allows testing magnification log events. */
     interface MagnificationLogger {
         void logMagnificationTripleTap(boolean enabled);
+        void logMagnificationTwoFingerTripleTap(boolean enabled);
     }
 
     interface State {
@@ -634,6 +645,62 @@
         }
     }
 
+    final class ViewportDraggingStateWithMultiFinger extends ViewportDraggingState {
+        // LINT.IfChange(viewport_dragging_state_with_multi_finger)
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+                throws GestureException {
+            final int action = event.getActionMasked();
+            switch (action) {
+                case ACTION_POINTER_DOWN: {
+                    clearAndTransitToPanningScalingState();
+                }
+                break;
+                case ACTION_MOVE: {
+                    if (event.getPointerCount() > 2) {
+                        throw new GestureException("Should have one pointer down.");
+                    }
+                    final float eventX = event.getX();
+                    final float eventY = event.getY();
+                    if (mFullScreenMagnificationController.magnificationRegionContains(
+                            mDisplayId, eventX, eventY)) {
+                        mFullScreenMagnificationController.setCenter(mDisplayId, eventX, eventY,
+                                /* animate */ mLastMoveOutsideMagnifiedRegion,
+                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+                        mLastMoveOutsideMagnifiedRegion = false;
+                    } else {
+                        mLastMoveOutsideMagnifiedRegion = true;
+                    }
+                }
+                break;
+
+                case ACTION_UP:
+                case ACTION_CANCEL: {
+                    // If mScaleToRecoverAfterDraggingEnd >= 1.0, the dragging state is triggered
+                    // by zoom in temporary, and the magnifier needs to recover to original scale
+                    // after exiting dragging state.
+                    // Otherwise, the magnifier should be disabled.
+                    if (mScaleToRecoverAfterDraggingEnd >= 1.0f) {
+                        zoomToScale(mScaleToRecoverAfterDraggingEnd, event.getX(),
+                                event.getY());
+                    } else {
+                        zoomOff();
+                    }
+                    clear();
+                    mScaleToRecoverAfterDraggingEnd = Float.NaN;
+                    transitionTo(mDetectingState);
+                }
+                    break;
+
+                case ACTION_DOWN: {
+                    throw new GestureException(
+                            "Unexpected event type: " + MotionEvent.actionToString(action));
+                }
+            }
+        }
+        // LINT.ThenChange(:viewport_dragging_state)
+    }
+
     /**
      * This class handles motion events when the event dispatcher has
      * determined that the user is performing a single-finger drag of the
@@ -643,17 +710,18 @@
      * of the finger, and any part of the screen is reachable without lifting the finger.
      * This makes it the preferable mode for tasks like reading text spanning full screen width.
      */
-    final class ViewportDraggingState implements State {
+    class ViewportDraggingState implements State {
 
         /**
          * The cached scale for recovering after dragging ends.
          * If the scale >= 1.0, the magnifier needs to recover to scale.
          * Otherwise, the magnifier should be disabled.
          */
-        @VisibleForTesting float mScaleToRecoverAfterDraggingEnd = Float.NaN;
+        @VisibleForTesting protected float mScaleToRecoverAfterDraggingEnd = Float.NaN;
 
-        private boolean mLastMoveOutsideMagnifiedRegion;
+        protected boolean mLastMoveOutsideMagnifiedRegion;
 
+        // LINT.IfChange(viewport_dragging_state)
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
                 throws GestureException {
@@ -706,6 +774,7 @@
                 }
             }
         }
+        // LINT.ThenChange(:viewport_dragging_state_with_multi_finger)
 
         private boolean isAlwaysOnMagnificationEnabled() {
             return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled();
@@ -732,7 +801,7 @@
                     ? mFullScreenMagnificationController.getScale(mDisplayId) : Float.NaN;
         }
 
-        private void clearAndTransitToPanningScalingState() {
+        protected void clearAndTransitToPanningScalingState() {
             final float scaleToRecovery = mScaleToRecoverAfterDraggingEnd;
             clear();
             mScaleToRecoverAfterDraggingEnd = scaleToRecovery;
@@ -791,36 +860,250 @@
         }
     }
 
+    final class DetectingStateWithMultiFinger extends DetectingState {
+        // A flag set to true when two fingers have touched down.
+        // Used to indicate what next finger action should be.
+        private boolean mIsTwoFingerCountReached = false;
+        // A tap counts when two fingers are down and up once.
+        private int mCompletedTapCount = 0;
+        DetectingStateWithMultiFinger(Context context) {
+            super(context);
+        }
+
+        // LINT.IfChange(detecting_state_with_multi_finger)
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN: {
+                    mLastDetectingDownEventTime = event.getDownTime();
+                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+
+                    mFirstPointerDownLocation.set(event.getX(), event.getY());
+
+                    if (!mFullScreenMagnificationController.magnificationRegionContains(
+                            mDisplayId, event.getX(), event.getY())) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (isMultiTapTriggered(2 /* taps */)) {
+
+                        // 3tap and hold
+                        afterLongTapTimeoutTransitionToDraggingState(event);
+
+                    } else if (isTapOutOfDistanceSlop()) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (mDetectSingleFingerTripleTap
+                            || mDetectTwoFingerTripleTap
+                            // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
+                            // to ensure reachability of
+                            // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
+                            || isActivated()) {
+
+                        afterMultiTapTimeoutTransitionToDelegatingState();
+
+                    } else {
+
+                        // Delegate pending events without delay
+                        transitionToDelegatingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_POINTER_DOWN: {
+                    mIsTwoFingerCountReached = mDetectTwoFingerTripleTap
+                            && event.getPointerCount() == 2;
+                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+
+                    if (isActivated() && event.getPointerCount() == 2) {
+                        storePointerDownLocation(mSecondPointerDownLocation, event);
+                        mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+                                ViewConfiguration.getTapTimeout());
+                    } else if (mIsTwoFingerCountReached) {
+                        // Placing two-finger triple-taps behind isActivated to avoid
+                        // blocking panning scaling state
+                        if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) {
+                            // 3tap and hold
+                            afterLongTapTimeoutTransitionToDraggingState(event);
+                        } else {
+                            afterMultiTapTimeoutTransitionToDelegatingState();
+                        }
+                    } else {
+                        transitionToDelegatingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_POINTER_UP: {
+                    // If it is a two-finger gesture, do not transition to the delegating state
+                    // to ensure the reachability of
+                    // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP)
+                    if (!mIsTwoFingerCountReached) {
+                        transitionToDelegatingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_MOVE: {
+                    if (isFingerDown()
+                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
+                        // Swipe detected - transition immediately
+
+                        // For convenience, viewport dragging takes precedence
+                        // over insta-delegating on 3tap&swipe
+                        // (which is a rare combo to be used aside from magnification)
+                        if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
+                            transitionToViewportDraggingStateAndClear(event);
+                        } else if (isActivated() && event.getPointerCount() == 2) {
+                            if (mIsSinglePanningEnabled
+                                    && overscrollState(event, mFirstPointerDownLocation)
+                                    == OVERSCROLL_VERTICAL_EDGE) {
+                                transitionToDelegatingStateAndClear();
+                            }
+                            //Primary pointer is swiping, so transit to PanningScalingState
+                            transitToPanningScalingStateAndClear();
+                        } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)
+                                && event.getPointerCount() == 2) {
+                            // Placing two-finger triple-taps behind isActivated to avoid
+                            // blocking panning scaling state
+                            transitionToViewportDraggingStateAndClear(event);
+                        } else if (mIsSinglePanningEnabled
+                                && isActivated()
+                                && event.getPointerCount() == 1) {
+                            if (overscrollState(event, mFirstPointerDownLocation)
+                                    == OVERSCROLL_VERTICAL_EDGE) {
+                                transitionToDelegatingStateAndClear();
+                            }
+                            transitToSinglePanningStateAndClear();
+                        } else {
+                            transitionToDelegatingStateAndClear();
+                        }
+                    } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation)
+                            && distanceClosestPointerToPoint(
+                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
+                        //Second pointer is swiping, so transit to PanningScalingState
+                        transitToPanningScalingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_UP: {
+
+                    mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+
+                    if (!mFullScreenMagnificationController.magnificationRegionContains(
+                            mDisplayId, event.getX(), event.getY())) {
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
+                        // Placing multiple fingers before a single finger, because achieving a
+                        // multi finger multi tap also means achieving a single finger triple tap
+                        onTripleTap(event);
+
+                    } else if (isMultiTapTriggered(3 /* taps */)) {
+                        onTripleTap(/* up */ event);
+
+                    } else if (
+                            // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
+                            isFingerDown()
+                            //TODO long tap should never happen here
+                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))
+                            // If it is a two-finger but not reach 3 tap, do not transition to the
+                            // delegating state to ensure the reachability of the triple tap
+                            && mCompletedTapCount == 0) {
+                        transitionToDelegatingStateAndClear();
+
+                    }
+                }
+                break;
+            }
+        }
+        // LINT.ThenChange(:detecting_state)
+
+        @Override
+        public void clear() {
+            mCompletedTapCount = 0;
+            setShortcutTriggered(false);
+            removePendingDelayedMessages();
+            clearDelayedMotionEvents();
+            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
+            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
+        }
+
+        private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) {
+            if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) {
+                mCompletedTapCount++;
+                mIsTwoFingerCountReached = false;
+            }
+
+            if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+                final boolean enabled = !isActivated();
+                mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+            }
+            return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
+        }
+
+        void transitionToDelegatingStateAndClear() {
+            mCompletedTapCount = 0;
+            transitionTo(mDelegatingState);
+            sendDelayedMotionEvents();
+            removePendingDelayedMessages();
+            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
+            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
+        }
+
+        void transitionToViewportDraggingStateAndClear(MotionEvent down) {
+
+            if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()");
+            final boolean shortcutTriggered = mShortcutTriggered;
+
+            // Only log the 3tap and hold event
+            if (!shortcutTriggered) {
+                final boolean enabled = !isActivated();
+                if (mCompletedTapCount == 2) {
+                    // Two finger triple tap and hold
+                    mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+                } else {
+                    // Triple tap and hold also belongs to triple tap event
+                    mMagnificationLogger.logMagnificationTripleTap(enabled);
+                }
+            }
+            clear();
+
+            mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered);
+            zoomInTemporary(down.getX(), down.getY(), shortcutTriggered);
+            transitionTo(mViewportDraggingState);
+        }
+    }
+
     /**
      * This class handles motion events when the event dispatch has not yet
      * determined what the user is doing. It watches for various tap events.
      */
-    final class DetectingState implements State, Handler.Callback {
+    class DetectingState implements State, Handler.Callback {
 
-        private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
-        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
-        private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
+        protected static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
+        protected static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+        protected static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
 
         final int mLongTapMinDelay;
         final int mSwipeMinDistance;
         final int mMultiTapMaxDelay;
         final int mMultiTapMaxDistance;
 
-        private MotionEventInfo mDelayedEventQueue;
-        MotionEvent mLastDown;
-        private MotionEvent mPreLastDown;
-        private MotionEvent mLastUp;
-        private MotionEvent mPreLastUp;
-        private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
+        protected MotionEventInfo mDelayedEventQueue;
+        protected MotionEvent mLastDown;
+        protected MotionEvent mPreLastDown;
+        protected MotionEvent mLastUp;
+        protected MotionEvent mPreLastUp;
 
-        private long mLastDetectingDownEventTime;
+        protected PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN);
+        protected PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
+        protected long mLastDetectingDownEventTime;
 
         @VisibleForTesting boolean mShortcutTriggered;
 
         @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this);
 
-        private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN);
-
         DetectingState(Context context) {
             mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
             mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
@@ -855,6 +1138,7 @@
             return true;
         }
 
+        // LINT.IfChange(detecting_state)
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             cacheDelayedMotionEvent(event, rawEvent, policyFlags);
@@ -969,23 +1253,24 @@
                 break;
             }
         }
+        // LINT.ThenChange(:detecting_state_with_multi_finger)
 
-        private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) {
+        protected void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) {
             final int index = event.getActionIndex();
             pointerDownLocation.set(event.getX(index), event.getY(index));
         }
 
-        private boolean pointerDownValid(PointF pointerDownLocation) {
+        protected boolean pointerDownValid(PointF pointerDownLocation) {
             return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(
                     pointerDownLocation.y));
         }
 
-        private void transitToPanningScalingStateAndClear() {
+        protected void transitToPanningScalingStateAndClear() {
             transitionTo(mPanningScalingState);
             clear();
         }
 
-        private void transitToSinglePanningStateAndClear() {
+        protected void transitToSinglePanningStateAndClear() {
             transitionTo(mSinglePanningState);
             clear();
         }
@@ -1016,7 +1301,7 @@
             return mLastDown != null;
         }
 
-        private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) {
+        protected long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) {
             if (a == null && b == null) return 0;
             return abs(timeOf(a) - timeOf(b));
         }
@@ -1061,13 +1346,13 @@
             mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
 
-        private void removePendingDelayedMessages() {
+        protected void removePendingDelayedMessages() {
             mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
             mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
             mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE);
         }
 
-        private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
+        protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
                 int policyFlags) {
             if (event.getActionMasked() == ACTION_DOWN) {
                 mPreLastDown = mLastDown;
@@ -1090,7 +1375,7 @@
             }
         }
 
-        private void sendDelayedMotionEvents() {
+        protected void sendDelayedMotionEvents() {
             if (mDelayedEventQueue == null) {
                 return;
             }
@@ -1112,7 +1397,7 @@
             } while (mDelayedEventQueue != null);
         }
 
-        private void clearDelayedMotionEvents() {
+        protected void clearDelayedMotionEvents() {
             while (mDelayedEventQueue != null) {
                 MotionEventInfo info = mDelayedEventQueue;
                 mDelayedEventQueue = info.mNext;
@@ -1136,7 +1421,7 @@
          *      1. direct three tap gesture
          *      2. one tap while shortcut triggered (it counts as two taps).
          */
-        private void onTripleTap(MotionEvent up) {
+        protected void onTripleTap(MotionEvent up) {
             if (DEBUG_DETECTING) {
                 Slog.i(mLogTag, "onTripleTap(); delayed: "
                         + MotionEventInfo.toString(mDelayedEventQueue));
@@ -1156,7 +1441,7 @@
             clear();
         }
 
-        private boolean isActivated() {
+        protected boolean isActivated() {
             return mFullScreenMagnificationController.isActivated(mDisplayId);
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 5635dd5..42ab05f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -205,7 +206,10 @@
                         intent,
                         PendingIntent.FLAG_MUTABLE
                                 | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
-                        /* options= */ null, UserHandle.CURRENT);
+                        ActivityOptions.makeBasic()
+                                .setPendingIntentCreatorBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle(), UserHandle.CURRENT);
                 if (sDebug) {
                     Slog.d(TAG, "startActivity add save UI restored with intent=" + intent);
                 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b9c269c..71a1f01 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -75,7 +75,6 @@
 import android.companion.IOnTransportsChangedListener;
 import android.companion.ISystemDataTransferCallback;
 import android.companion.datatransfer.PermissionSyncRequest;
-import android.companion.utils.FeatureUtils;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -829,11 +828,6 @@
         @Override
         public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
                 int userId, int associationId) {
-            if (!FeatureUtils.isPermSyncEnabled()) {
-                throw new UnsupportedOperationException("Calling"
-                        + " buildPermissionTransferUserConsentIntent, but this API is disabled by"
-                        + " the system.");
-            }
             return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
                     packageName, userId, associationId);
         }
@@ -841,10 +835,6 @@
         @Override
         public void startSystemDataTransfer(String packageName, int userId, int associationId,
                 ISystemDataTransferCallback callback) {
-            if (!FeatureUtils.isPermSyncEnabled()) {
-                throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this"
-                        + " API is disabled by the system.");
-            }
             mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
                     associationId, callback);
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 1f62613..23e7ce6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -20,6 +20,7 @@
 
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
+import android.companion.Flags;
 import android.companion.Telecom;
 import android.companion.datatransfer.PermissionSyncRequest;
 import android.net.MacAddress;
@@ -65,7 +66,14 @@
     public int onCommand(String cmd) {
         final PrintWriter out = getOutPrintWriter();
         final int associationId;
+
         try {
+            if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
+                associationId = getNextIntArgRequired();
+                int event = getNextIntArgRequired();
+                mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+                return 0;
+            }
             switch (cmd) {
                 case "list": {
                     final int userId = getNextIntArgRequired();
@@ -107,10 +115,15 @@
                     mService.loadAssociationsFromDisk();
                     break;
 
-                case "simulate-device-event":
+                case "simulate-device-appeared":
                     associationId = getNextIntArgRequired();
-                    int event = getNextIntArgRequired();
-                    mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+                    mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
+                    break;
+
+                case "simulate-device-disappeared":
+                    associationId = getNextIntArgRequired();
+                    mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
+                    break;
 
                 case "remove-inactive-associations": {
                     // This command should trigger the same "clean-up" job as performed by the
@@ -346,9 +359,7 @@
         pw.println("      information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
         pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
 
-        pw.println("  simulate-device-event ASSOCIATION_ID EVENT");
-        pw.println("  Simulate the companion device event changes:");
-        pw.println("    Case(0): ");
+        pw.println("  simulate-device-appeared ASSOCIATION_ID");
         pw.println("      Make CDM act as if the given companion device has appeared.");
         pw.println("      I.e. bind the associated companion application's");
         pw.println("      CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
@@ -356,18 +367,43 @@
         pw.println("      will act as if device disappeared, unless 'simulate-device-disappeared'");
         pw.println("      or 'simulate-device-appeared' is called again before 60 seconds run out"
                 + ".");
-        pw.println("    Case(1): ");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+        pw.println("  simulate-device-disappeared ASSOCIATION_ID");
         pw.println("      Make CDM act as if the given companion device has disappeared.");
         pw.println("      I.e. unbind the associated companion application's");
         pw.println("      CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
         pw.println("      NOTE: This will only have effect if 'simulate-device-appeared' was");
         pw.println("      invoked for the same device (same ASSOCIATION_ID) no longer than");
         pw.println("      60 seconds ago.");
-        pw.println("    Case(2): ");
-        pw.println("      Make CDM act as if the given companion device is BT connected ");
-        pw.println("    Case(3): ");
-        pw.println("      Make CDM act as if the given companion device is BT disconnected ");
-        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+        if (Flags.devicePresence()) {
+            pw.println("  simulate-device-event ASSOCIATION_ID EVENT");
+            pw.println("  Simulate the companion device event changes:");
+            pw.println("    Case(0): ");
+            pw.println("      Make CDM act as if the given companion device has appeared.");
+            pw.println("      I.e. bind the associated companion application's");
+            pw.println("      CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
+            pw.println("      The CDM will consider the devices as present for"
+                    + "60 seconds and then");
+            pw.println("      will act as if device disappeared, unless"
+                    + "'simulate-device-disappeared'");
+            pw.println("      or 'simulate-device-appeared' is called again before 60 seconds"
+                    + "run out.");
+            pw.println("    Case(1): ");
+            pw.println("      Make CDM act as if the given companion device has disappeared.");
+            pw.println("      I.e. unbind the associated companion application's");
+            pw.println("      CompanionDeviceService(s) and trigger onDeviceDisappeared()"
+                    + "callback.");
+            pw.println("      NOTE: This will only have effect if 'simulate-device-appeared' was");
+            pw.println("      invoked for the same device (same ASSOCIATION_ID) no longer than");
+            pw.println("      60 seconds ago.");
+            pw.println("    Case(2): ");
+            pw.println("      Make CDM act as if the given companion device is BT connected ");
+            pw.println("    Case(3): ");
+            pw.println("      Make CDM act as if the given companion device is BT disconnected ");
+            pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+        }
 
         pw.println("  remove-inactive-associations");
         pw.println("      Remove self-managed associations that have not been active ");
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 8fea078..e42b935 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -79,7 +79,7 @@
         void onDeviceDisappeared(int associationId);
 
         /**Invoked when device has corresponding event changes. */
-        void onDeviceEvent(int associationId, int state);
+        void onDeviceEvent(int associationId, int event);
     }
 
     private final @NonNull AssociationStore mAssociationStore;
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index eeaa423..c111ec3 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -49,7 +49,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.StandardCharsets;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
@@ -83,14 +82,6 @@
     @interface PhysType {
     }
 
-    /**
-     * The maximum length of a device name (in bytes in UTF-8 encoding).
-     *
-     * This limitation comes directly from uinput.
-     * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h
-     */
-    private static final int DEVICE_NAME_MAX_LENGTH = 80;
-
     final Object mLock = new Object();
 
     /* Token -> file descriptor associations. */
@@ -138,25 +129,17 @@
         }
     }
 
-    void createDpad(@NonNull String deviceName,
-                        int vendorId,
-                        int productId,
-                        @NonNull IBinder deviceToken,
-                        int displayId) {
+    void createDpad(@NonNull String deviceName, int vendorId, int productId,
+            @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException {
         final String phys = createPhys(PHYS_TYPE_DPAD);
-        try {
-            createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId,
+        createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId,
                     productId, deviceToken, displayId, phys,
                     () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys));
-        } catch (DeviceCreationException e) {
-            throw new RuntimeException(
-                    "Failed to create virtual dpad device '" + deviceName + "'.", e);
-        }
     }
 
     void createKeyboard(@NonNull String deviceName, int vendorId, int productId,
             @NonNull IBinder deviceToken, int displayId, @NonNull String languageTag,
-            @NonNull String layoutType) {
+            @NonNull String layoutType) throws DeviceCreationException {
         final String phys = createPhys(PHYS_TYPE_KEYBOARD);
         mInputManagerInternal.addKeyboardLayoutAssociation(phys, languageTag,
                 layoutType);
@@ -166,66 +149,42 @@
                     () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
         } catch (DeviceCreationException e) {
             mInputManagerInternal.removeKeyboardLayoutAssociation(phys);
-            throw new RuntimeException(
-                    "Failed to create virtual keyboard device '" + deviceName + "'.", e);
+            throw e;
         }
     }
 
-    void createMouse(@NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken,
-            int displayId) {
+    void createMouse(@NonNull String deviceName, int vendorId, int productId,
+            @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException {
         final String phys = createPhys(PHYS_TYPE_MOUSE);
-        try {
-            createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
-                    deviceToken, displayId, phys,
-                    () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
-        } catch (DeviceCreationException e) {
-            throw new RuntimeException(
-                    "Failed to create virtual mouse device: '" + deviceName + "'.", e);
-        }
+        createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
+                deviceToken, displayId, phys,
+                () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
         mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
     }
 
-    void createTouchscreen(@NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken,
-            int displayId,
-            int height,
-            int width) {
+    void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
+            @NonNull IBinder deviceToken, int displayId, int height, int width)
+            throws DeviceCreationException {
         final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
-        try {
-            createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
-                    productId, deviceToken, displayId, phys,
-                    () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
-                            phys, height, width));
-        } catch (DeviceCreationException e) {
-            throw new RuntimeException(
-                    "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
-        }
+        createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
+                productId, deviceToken, displayId, phys,
+                () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
+                        height, width));
     }
 
-    void createNavigationTouchpad(
-            @NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken,
-            int displayId,
-            int touchpadHeight,
-            int touchpadWidth) {
+    void createNavigationTouchpad(@NonNull String deviceName, int vendorId, int productId,
+            @NonNull IBinder deviceToken, int displayId, int height, int width)
+            throws DeviceCreationException {
         final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD);
         mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE);
         try {
             createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName,
                     vendorId, productId, deviceToken, displayId, phys,
                     () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
-                            phys, touchpadHeight, touchpadWidth));
+                            phys, height, width));
         } catch (DeviceCreationException e) {
             mInputManagerInternal.unsetTypeAssociation(phys);
-            throw new RuntimeException(
-                    "Failed to create virtual navigation touchpad device '" + deviceName + "'.", e);
+            throw e;
         }
     }
 
@@ -234,10 +193,10 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not unregister input device for given token");
+                Slog.w(TAG, "Could not unregister input device for given token.");
+            } else {
+                closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);
             }
-            closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);
         }
     }
 
@@ -326,21 +285,11 @@
     }
 
     /**
-     * Validates a device name by checking length and whether a device with the same name
-     * already exists. Throws exceptions if the validation fails.
+     * Validates a device name by checking whether a device with the same name already exists.
      * @param deviceName The name of the device to be validated
      * @throws DeviceCreationException if {@code deviceName} is not valid.
      */
     private void validateDeviceName(String deviceName) throws DeviceCreationException {
-        // Comparison is greater or equal because the device name must fit into a const char*
-        // including the \0-terminator. Therefore the actual number of bytes that can be used
-        // for device name is DEVICE_NAME_MAX_LENGTH - 1
-        if (deviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) {
-            throw new DeviceCreationException(
-                    "Input device name exceeds maximum length of " + DEVICE_NAME_MAX_LENGTH
-                            + "bytes: " + deviceName);
-        }
-
         synchronized (mLock) {
             for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) {
                 if (mInputDeviceDescriptors.valueAt(i).mName.equals(deviceName)) {
@@ -365,8 +314,7 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send key event to input device for given token");
+                return false;
             }
             return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getKeyCode(), event.getAction(), event.getEventTimeNanos());
@@ -378,8 +326,7 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send key event to input device for given token");
+                return false;
             }
             return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getKeyCode(), event.getAction(), event.getEventTimeNanos());
@@ -391,13 +338,12 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send button event to input device for given token");
+                return false;
             }
             if (inputDeviceDescriptor.getDisplayId()
                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                throw new IllegalStateException(
-                        "Display id associated with this mouse is not currently targetable");
+                mInputManagerInternal.setVirtualMousePointerDisplayId(
+                        inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
@@ -409,8 +355,7 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send touch event to input device for given token");
+                return false;
             }
             return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getPointerId(), event.getToolType(), event.getAction(), event.getX(),
@@ -424,13 +369,12 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send relative event to input device for given token");
+                return false;
             }
             if (inputDeviceDescriptor.getDisplayId()
                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                throw new IllegalStateException(
-                        "Display id associated with this mouse is not currently targetable");
+                mInputManagerInternal.setVirtualMousePointerDisplayId(
+                        inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
@@ -442,13 +386,12 @@
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
                     token);
             if (inputDeviceDescriptor == null) {
-                throw new IllegalArgumentException(
-                        "Could not send scroll event to input device for given token");
+                return false;
             }
             if (inputDeviceDescriptor.getDisplayId()
                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                throw new IllegalStateException(
-                        "Display id associated with this mouse is not currently targetable");
+                mInputManagerInternal.setVirtualMousePointerDisplayId(
+                        inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
@@ -465,8 +408,8 @@
             }
             if (inputDeviceDescriptor.getDisplayId()
                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                throw new IllegalStateException(
-                        "Display id associated with this mouse is not currently targetable");
+                mInputManagerInternal.setVirtualMousePointerDisplayId(
+                        inputDeviceDescriptor.getDisplayId());
             }
             return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
         }
@@ -758,13 +701,19 @@
     }
 
     /** An internal exception that is thrown to indicate an error when opening a virtual device. */
-    private static class DeviceCreationException extends Exception {
+    static class DeviceCreationException extends Exception {
+        DeviceCreationException() {
+            super();
+        }
         DeviceCreationException(String message) {
             super(message);
         }
-        DeviceCreationException(String message, Exception cause) {
+        DeviceCreationException(String message, Throwable cause) {
             super(message, cause);
         }
+        DeviceCreationException(Throwable cause) {
+            super(cause);
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 5295ec8..4fe0592 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,7 +2,7 @@
 
 set noparent
 
-ogunwale@google.com
-michaelwr@google.com
+marvinramin@google.com
 vladokom@google.com
-marvinramin@google.com
\ No newline at end of file
+ogunwale@google.com
+michaelwr@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 3583a78..a159a5e 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -55,5 +55,15 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsVirtualDevicesCameraTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 118943d..45d7314 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -39,7 +39,6 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
-import android.app.compat.CompatChanges;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -55,8 +54,6 @@
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
@@ -81,7 +78,6 @@
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
-import android.os.Build;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Looper;
@@ -122,22 +118,6 @@
 
     private static final String TAG = "VirtualDeviceImpl";
 
-    /**
-     * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
-     * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
-     * for the creation of private, auto-mirror, and fixed orientation displays since
-     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
-     *
-     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
-     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
-     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER =
-            294837146L;
-
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
             DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
@@ -365,8 +345,7 @@
         }
 
         int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
-        if (!CompatChanges.isChangeEnabled(
-                MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER, mOwnerUid)) {
+        if (!Flags.consistentDisplayFlags()) {
             flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC;
         }
         if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
@@ -694,6 +673,8 @@
             mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken,
                     getTargetDisplayIdForInput(config.getAssociatedDisplayId()));
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -705,15 +686,17 @@
         super.createVirtualKeyboard_enforcePermission();
         Objects.requireNonNull(config);
         checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
-        synchronized (mVirtualDeviceLock) {
-            mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag());
-        }
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken,
                     getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
                     config.getLanguageTag(), config.getLayoutType());
+            synchronized (mVirtualDeviceLock) {
+                mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag());
+            }
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -729,6 +712,8 @@
         try {
             mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken, config.getAssociatedDisplayId());
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -741,19 +726,13 @@
         super.createVirtualTouchscreen_enforcePermission();
         Objects.requireNonNull(config);
         checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
-        int screenHeight = config.getHeight();
-        int screenWidth = config.getWidth();
-        if (screenHeight <= 0 || screenWidth <= 0) {
-            throw new IllegalArgumentException(
-                    "Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
-                            + "(" + screenWidth + ", " + screenHeight + ")");
-        }
-
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
-                    screenHeight, screenWidth);
+                    config.getHeight(), config.getWidth());
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -766,21 +745,15 @@
         super.createVirtualNavigationTouchpad_enforcePermission();
         Objects.requireNonNull(config);
         checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
-        int touchpadHeight = config.getHeight();
-        int touchpadWidth = config.getWidth();
-        if (touchpadHeight <= 0 || touchpadWidth <= 0) {
-            throw new IllegalArgumentException(
-                "Cannot create a virtual navigation touchpad, touchpad dimensions must be positive."
-                    + " Got: (" + touchpadHeight + ", " + touchpadWidth + ")");
-        }
-
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createNavigationTouchpad(
                     config.getInputDeviceName(), config.getVendorId(),
                     config.getProductId(), deviceToken,
                     getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
-                    touchpadHeight, touchpadWidth);
+                    config.getHeight(), config.getWidth());
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -966,7 +939,7 @@
         if (mVirtualCameraController == null) {
             throw new UnsupportedOperationException("Virtual camera controller is not available");
         }
-        mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig));
+        mVirtualCameraController.registerCamera(cameraConfig);
     }
 
     @Override // Binder call
@@ -978,7 +951,19 @@
         if (mVirtualCameraController == null) {
             throw new UnsupportedOperationException("Virtual camera controller is not available");
         }
-        mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig));
+        mVirtualCameraController.unregisterCamera(cameraConfig);
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public int getVirtualCameraId(@NonNull VirtualCameraConfig cameraConfig)
+            throws RemoteException {
+        super.getVirtualCameraId_enforcePermission();
+        Objects.requireNonNull(cameraConfig);
+        if (mVirtualCameraController == null) {
+            throw new UnsupportedOperationException("Virtual camera controller is not available");
+        }
+        return mVirtualCameraController.getCameraId(cameraConfig);
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 215970e..9b78ed4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -187,9 +187,7 @@
             CompanionDeviceManager cdm =
                     getContext().getSystemService(CompanionDeviceManager.class);
             if (cdm != null) {
-                synchronized (mVirtualDeviceManagerLock) {
-                    mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL);
-                }
+                onCdmAssociationsChanged(cdm.getAllAssociations(UserHandle.USER_ALL));
                 cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
                         this::onCdmAssociationsChanged, UserHandle.USER_ALL);
             } else {
@@ -345,19 +343,21 @@
 
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     void onCdmAssociationsChanged(List<AssociationInfo> associations) {
+        List<AssociationInfo> vdmAssociations = new ArrayList<>();
+        Set<Integer> activeAssociationIds = new HashSet<>();
+        for (int i = 0; i < associations.size(); ++i) {
+            AssociationInfo association = associations.get(i);
+            if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())) {
+                vdmAssociations.add(association);
+                activeAssociationIds.add(association.getId());
+            }
+        }
         Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
         Set<String> removedPersistentDeviceIds = new HashSet<>();
         synchronized (mVirtualDeviceManagerLock) {
-            Set<Integer> activeAssociationIds = new HashSet<>(associations.size());
-            for (int i = 0; i < associations.size(); ++i) {
-                activeAssociationIds.add(associations.get(i).getId());
-            }
-
             for (int i = 0; i < mActiveAssociations.size(); ++i) {
                 AssociationInfo associationInfo = mActiveAssociations.get(i);
-                if (!activeAssociationIds.contains(associationInfo.getId())
-                        && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(
-                                associationInfo.getDeviceProfile())) {
+                if (!activeAssociationIds.contains(associationInfo.getId())) {
                     removedPersistentDeviceIds.add(
                             VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
                 }
@@ -370,7 +370,7 @@
                 }
             }
 
-            mActiveAssociations = associations;
+            mActiveAssociations = vdmAssociations;
         }
 
         for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
@@ -460,11 +460,11 @@
 
             synchronized (mVirtualDeviceManagerLock) {
                 if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
-                    final long callindId = Binder.clearCallingIdentity();
+                    final long callingId = Binder.clearCallingIdentity();
                     try {
                         registerCdmAssociationListener();
                     } finally {
-                        Binder.restoreCallingIdentity(callindId);
+                        Binder.restoreCallingIdentity(callingId);
                     }
                 }
                 mVirtualDevices.put(deviceId, virtualDevice);
@@ -498,13 +498,16 @@
             synchronized (mVirtualDeviceManagerLock) {
                 virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
                 if (virtualDeviceImpl == null) {
-                    throw new SecurityException("Invalid VirtualDevice");
+                    throw new SecurityException(
+                            "Invalid VirtualDevice (deviceId = " + virtualDevice.getDeviceId()
+                                    + ")");
                 }
             }
             if (virtualDeviceImpl.getOwnerUid() != callingUid) {
                 throw new SecurityException(
                         "uid " + callingUid
-                                + " is not the owner of the supplied VirtualDevice");
+                                + " is not the owner of the supplied VirtualDevice (deviceId = "
+                                + virtualDevice.getDeviceId() + ")");
             }
 
             return virtualDeviceImpl.createVirtualDisplay(
@@ -851,6 +854,11 @@
         }
 
         @Override
+        public int getDeviceIdForDisplayId(int displayId) {
+            return mImpl.getDeviceIdForDisplayId(displayId);
+        }
+
+        @Override
         public @Nullable String getPersistentIdForDevice(int deviceId) {
             if (deviceId == Context.DEVICE_ID_DEFAULT) {
                 return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
@@ -864,6 +872,19 @@
         }
 
         @Override
+        public @NonNull Set<String> getAllPersistentDeviceIds() {
+            Set<String> persistentIds = new ArraySet<>();
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mActiveAssociations.size(); ++i) {
+                    AssociationInfo associationInfo = mActiveAssociations.get(i);
+                    persistentIds.add(
+                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
+                }
+            }
+            return persistentIds;
+        }
+
+        @Override
         public void registerAppsOnVirtualDeviceListener(
                 @NonNull AppsOnVirtualDeviceListener listener) {
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 06be3f3..5665ad5 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -67,14 +67,7 @@
      * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
      */
     public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
-        // Try to connect to service if not connected already.
-        if (mVirtualCameraService == null) {
-            connectVirtualCameraService();
-        }
-        // Throw exception if we are unable to connect to service.
-        if (mVirtualCameraService == null) {
-            throw new IllegalStateException("Virtual camera service is not connected.");
-        }
+        connectVirtualCameraServiceIfNeeded();
 
         try {
             if (registerCameraWithService(cameraConfig)) {
@@ -110,6 +103,17 @@
         }
     }
 
+    /** Return the id of the virtual camera with the given config. */
+    public int getCameraId(@NonNull VirtualCameraConfig cameraConfig) {
+        connectVirtualCameraServiceIfNeeded();
+
+        try {
+            return mVirtualCameraService.getCameraId(cameraConfig.getCallback().asBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     public void binderDied() {
         Slog.d(TAG, "Virtual camera service died.");
@@ -152,6 +156,17 @@
         }
     }
 
+    private void connectVirtualCameraServiceIfNeeded() {
+        // Try to connect to service if not connected already.
+        if (mVirtualCameraService == null) {
+            connectVirtualCameraService();
+        }
+        // Throw exception if we are unable to connect to service.
+        if (mVirtualCameraService == null) {
+            throw new IllegalStateException("Virtual camera service is not connected.");
+        }
+    }
+
     private void connectVirtualCameraService() {
         final long callingId = Binder.clearCallingIdentity();
         try {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index 202f68b..6940ffe 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -20,9 +20,11 @@
 import android.companion.virtual.camera.IVirtualCameraCallback;
 import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtualcamera.Format;
 import android.companion.virtualcamera.IVirtualCameraService;
 import android.companion.virtualcamera.SupportedStreamConfiguration;
 import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.graphics.ImageFormat;
 import android.os.RemoteException;
 import android.view.Surface;
 
@@ -67,6 +69,11 @@
             }
 
             @Override
+            public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
+                camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+            }
+
+            @Override
             public void onStreamClosed(int streamId) throws RemoteException {
                 camera.onStreamClosed(streamId);
             }
@@ -85,10 +92,14 @@
         SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration();
         supportedConfig.height = stream.getHeight();
         supportedConfig.width = stream.getWidth();
-        supportedConfig.pixelFormat = stream.getFormat();
+        supportedConfig.pixelFormat = convertFormat(stream.getFormat());
         return supportedConfig;
     }
 
+    private static int convertFormat(int format) {
+        return format == ImageFormat.YUV_420_888 ? Format.YUV_420_888 : Format.UNKNOWN;
+    }
+
     private VirtualCameraConversionUtil() {
     }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 49457fb..3323d0b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -125,6 +125,9 @@
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
+
+        // Java/AIDL sources to be moved out to CrashRecovery module
+        ":services-crashrecovery-sources",
     ],
 
     libs: [
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index fc56511..23c008e 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -27,6 +27,7 @@
 import android.content.LocusId;
 import android.content.res.Configuration;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -134,6 +135,18 @@
             @Nullable LocusId locusId, @NonNull IBinder appToken);
 
     /**
+     * Report a user interaction event to UsageStatsManager
+     *
+     * @param pkgName The package for which this user interaction event occurred.
+     * @param userId The user id to which component belongs to.
+     * @param extras The extra details about this user interaction event.
+     * {@link UsageEvents.Event#USER_INTERACTION}
+     * {@link UsageStatsManager#reportUserInteraction(String, int, PersistableBundle)}
+     */
+    public abstract void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId,
+            @NonNull PersistableBundle extras);
+
+    /**
      * Prepares the UsageStatsService for shutdown.
      */
     public abstract void prepareShutdown();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 7e4cf4f..898b693 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -48,6 +48,7 @@
 import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.KnownPackages;
+import com.android.server.pm.PackageArchiver;
 import com.android.server.pm.PackageList;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.dex.DynamicCodeLogger;
@@ -1442,4 +1443,10 @@
      */
     public abstract void sendPackageDataClearedBroadcast(@NonNull String packageName,
             int uid, int userId, boolean isRestore, boolean isInstantApp);
+
+    /**
+     * Returns an instance of {@link PackageArchiver} to be used for archiving related operations.
+     */
+    @NonNull
+    public abstract PackageArchiver getPackageArchiver();
 }
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 7907d61..eb3ec24 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1182,8 +1182,8 @@
 
         // we are only interested in doing things at PHASE_BOOT_COMPLETED
         if (phase == PHASE_BOOT_COMPLETED) {
-            Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
-            getVBMetaDigestInformation();
+            Slog.i(TAG, "Boot completed. Getting boot integrity data.");
+            collectBootIntegrityInfo();
 
             // Log to statsd
             // TODO(b/264061957): For now, biometric system properties are always collected if users
@@ -1458,10 +1458,22 @@
         }
     }
 
-    private void getVBMetaDigestInformation() {
+    private void collectBootIntegrityInfo() {
         mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
         Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
         FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
+
+        if (android.security.Flags.binaryTransparencySepolicyHash()) {
+            byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
+                    "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
+            String sepolicyHashEncoded = null;
+            if (sepolicyHash != null) {
+                sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
+                Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+            }
+            FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
+                    sepolicyHashEncoded, mVbmetaDigest);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 987507f..c258370 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,5 +1,5 @@
-# BootReceiver
-per-file BootReceiver.java = gaillard@google.com
+# BootReceiver / Watchdog
+per-file BootReceiver.java,Watchdog.java = gaillard@google.com
 
 # Connectivity / Networking
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java,VpnManagerService.java = file:/services/core/java/com/android/server/net/OWNERS
@@ -35,7 +35,7 @@
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
-per-file PinnerService.java = file:/apct-tests/perftests/OWNERS
+per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS
 per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com
 per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
 per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 8cd5ce1..23a30f9 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,9 @@
 import static android.app.ActivityManager.UID_OBSERVER_GONE;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.server.flags.Flags.pinWebview;
+
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +31,8 @@
 import android.app.IActivityManager;
 import android.app.SearchManager;
 import android.app.UidObserver;
+import android.app.pinner.IPinnerService;
+import android.app.pinner.PinnedFileStat;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -48,6 +53,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -83,6 +89,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -94,6 +102,7 @@
  * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
  * overlay.</p>
  * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
+ * <p>(Optional) Pin experimental carveout regions based on DeviceConfig flags.</p>
  */
 public final class PinnerService extends SystemService {
     private static final boolean DEBUG = false;
@@ -110,16 +119,15 @@
     private static final int KEY_ASSISTANT = 2;
 
     // Pin using pinlist.meta when pinning apps.
-    private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean(
-            "pinner.use_pinlist", true);
-    // Pin the whole odex/vdex/etc file when pinning apps.
-    private static boolean PROP_PIN_ODEX = SystemProperties.getBoolean(
-            "pinner.whole_odex", true);
+    private static boolean PROP_PIN_PINLIST =
+            SystemProperties.getBoolean("pinner.use_pinlist", true);
 
     private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
     private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
     private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app.
 
+    public static final String ANON_REGION_STAT_NAME = "[anon]";
+
     @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
     @Retention(RetentionPolicy.SOURCE)
     public @interface AppKey {}
@@ -134,8 +142,7 @@
     private SearchManager mSearchManager;
 
     /** The list of the statically pinned files. */
-    @GuardedBy("this")
-    private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
+    @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
 
     /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
     @GuardedBy("this")
@@ -158,6 +165,10 @@
     @GuardedBy("this")
     private ArraySet<Integer> mPinKeys;
 
+    // Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
+    // them to be responsive to dynamic flag changes for experimentation.
+    private static final String DEVICE_CONFIG_NAMESPACE_ANON_SIZE =
+            DeviceConfig.NAMESPACE_RUNTIME_NATIVE;
     private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size";
     private static final long DEFAULT_ANON_SIZE =
             SystemProperties.getLong("pinner.pin_shared_anon_size", 0);
@@ -170,6 +181,7 @@
     private final boolean mConfiguredToPinCamera;
     private final boolean mConfiguredToPinHome;
     private final boolean mConfiguredToPinAssistant;
+    private final int mConfiguredWebviewPinBytes;
 
     private BinderService mBinderService;
     private PinnerHandler mPinnerHandler = null;
@@ -188,11 +200,11 @@
         }
     };
 
-    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+    private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigAnonSizeListener =
             new DeviceConfig.OnPropertiesChangedListener() {
                 @Override
                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
-                    if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace())
+                    if (DEVICE_CONFIG_NAMESPACE_ANON_SIZE.equals(properties.getNamespace())
                             && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) {
                         refreshPinAnonConfig();
                     }
@@ -209,6 +221,11 @@
         protected void publishBinderService(PinnerService service, Binder binderService) {
             service.publishBinderService("pinner", binderService);
         }
+
+        protected PinnedFile pinFileInternal(String fileToPin,
+                int maxBytesToPin, boolean attemptPinIntrospection) {
+            return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
+        }
     }
 
     public PinnerService(Context context) {
@@ -228,6 +245,8 @@
                 com.android.internal.R.bool.config_pinnerHomeApp);
         mConfiguredToPinAssistant = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_pinnerAssistantApp);
+        mConfiguredWebviewPinBytes = context.getResources().getInteger(
+                com.android.internal.R.integer.config_pinnerWebviewPinBytes);
         mPinKeys = createPinKeys();
         mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
 
@@ -246,9 +265,9 @@
         registerUserSetupCompleteListener();
 
         mDeviceConfigInterface.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
                 new HandlerExecutor(mPinnerHandler),
-                mDeviceConfigListener);
+                mDeviceConfigAnonSizeListener);
     }
 
     @Override
@@ -317,7 +336,7 @@
     public List<PinnedFileStats> dumpDataForStatsd() {
         List<PinnedFileStats> pinnedFileStats = new ArrayList<>();
         synchronized (PinnerService.this) {
-            for (PinnedFile pinnedFile : mPinnedFiles) {
+            for (PinnedFile pinnedFile : mPinnedFiles.values()) {
                 pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile));
             }
 
@@ -353,39 +372,17 @@
             com.android.internal.R.array.config_defaultPinnerServiceFiles);
         // Continue trying to pin each file even if we fail to pin some of them
         for (String fileToPin : filesToPin) {
-            PinnedFile pf = pinFile(fileToPin,
-                                    Integer.MAX_VALUE,
-                                    /*attemptPinIntrospection=*/false);
+            PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE,
+                    /*attemptPinIntrospection=*/false);
             if (pf == null) {
                 Slog.e(TAG, "Failed to pin file = " + fileToPin);
                 continue;
             }
             synchronized (this) {
-                mPinnedFiles.add(pf);
+                mPinnedFiles.put(pf.fileName, pf);
             }
-            if (fileToPin.endsWith(".jar") | fileToPin.endsWith(".apk")) {
-                // Check whether the runtime has compilation artifacts to pin.
-                String arch = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
-                String[] files = null;
-                try {
-                    files = DexFile.getDexFileOutputPaths(fileToPin, arch);
-                } catch (IOException ioe) { }
-                if (files == null) {
-                    continue;
-                }
-                for (String file : files) {
-                    PinnedFile df = pinFile(file,
-                                            Integer.MAX_VALUE,
-                                            /*attemptPinIntrospection=*/false);
-                    if (df == null) {
-                        Slog.i(TAG, "Failed to pin ART file = " + file);
-                        continue;
-                    }
-                    synchronized (this) {
-                        mPinnedFiles.add(df);
-                    }
-                }
-            }
+            pf.groupName = "system";
+            pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null);
         }
 
         refreshPinAnonConfig();
@@ -482,7 +479,7 @@
             pinnedAppFiles = new ArrayList<>(app.mFiles);
         }
         for (PinnedFile pinnedFile : pinnedAppFiles) {
-            pinnedFile.close();
+            unpinFile(pinnedFile.fileName);
         }
     }
 
@@ -490,6 +487,19 @@
         return ResolverActivity.class.getName().equals(info.name);
     }
 
+    public int getWebviewPinQuota() {
+        if (!pinWebview()) {
+            return 0;
+        }
+        int quota = mConfiguredWebviewPinBytes;
+        int overrideQuota = SystemProperties.getInt("pinner.pin_webview_size", -1);
+        if (overrideQuota != -1) {
+            // Quota was overridden
+            quota = overrideQuota;
+        }
+        return quota;
+    }
+
     private ApplicationInfo getCameraInfo(int userHandle) {
         Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
         ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
@@ -723,7 +733,7 @@
             case KEY_ASSISTANT:
                 return "Assistant";
             default:
-                return null;
+                return "";
         }
     }
 
@@ -733,7 +743,7 @@
     private void refreshPinAnonConfig() {
         long newPinAnonSize =
                 mDeviceConfigInterface.getLong(
-                        DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                        DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
                         DEVICE_CONFIG_KEY_ANON_SIZE,
                         DEFAULT_ANON_SIZE);
         newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
@@ -863,11 +873,12 @@
                 continue;
             }
 
-            PinnedFile pf = pinFile(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
+            PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
             if (pf == null) {
                 Slog.e(TAG, "Failed to pin " + apk);
                 continue;
             }
+            pf.groupName = getNameForKey(key);
 
             if (DEBUG) {
                 Slog.i(TAG, "Pinned " + pf.fileName);
@@ -877,40 +888,118 @@
             }
 
             apkPinSizeLimit -= pf.bytesPinned;
+            if (apk.equals(appInfo.sourceDir)) {
+                pinOptimizedDexDependencies(pf, apkPinSizeLimit, appInfo);
+            }
         }
+    }
 
-        // determine the ABI from either ApplicationInfo or Build
-        String abi = appInfo.primaryCpuAbi != null ? appInfo.primaryCpuAbi :
-                Build.SUPPORTED_ABIS[0];
-        String arch = VMRuntime.getInstructionSet(abi);
-        // get the path to the odex or oat file
-        String baseCodePath = appInfo.getBaseCodePath();
-        String[] files = null;
-        try {
-            files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
-        } catch (IOException ioe) {}
-        if (files == null) {
-            return;
+    /**
+     * Pin file or apk to memory.
+     *
+     * Prefer to use this method instead of {@link #pinFileInternal(String, int, boolean)} as it
+     * takes care of accounting and if pinning an apk, it also pins any extra optimized art files
+     * that related to the file but not within itself.
+     *
+     * @param fileToPin File to pin
+     * @param maxBytesToPin maximum quota allowed for pinning
+     * @return total bytes that were pinned.
+     */
+    public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo,
+            @Nullable String groupName) {
+        PinnedFile existingPin;
+        synchronized(this) {
+            existingPin = mPinnedFiles.get(fileToPin);
         }
-
-        //not pinning the oat/odex is not a fatal error
-        for (String file : files) {
-            PinnedFile pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
-            if (pf != null) {
-                synchronized (this) {
-                    if (PROP_PIN_ODEX) {
-                      pinnedApp.mFiles.add(pf);
-                    }
-                }
+        if (existingPin != null) {
+            if (existingPin.bytesPinned == maxBytesToPin) {
+                // Duplicate pin requesting same amount of bytes, lets just bail out.
+                return 0;
+            } else {
+                // User decided to pin a different amount of bytes than currently pinned
+                // so this is a valid pin request. Unpin the previous version before repining.
                 if (DEBUG) {
-                    if (PROP_PIN_ODEX) {
-                        Slog.i(TAG, "Pinned " + pf.fileName);
-                    } else {
-                        Slog.i(TAG, "Pinned [skip] " + pf.fileName);
-                    }
+                    Slog.d(TAG, "Unpinning file prior to repin: " + fileToPin);
+                }
+                unpinFile(fileToPin);
+            }
+        }
+
+        boolean isApk = fileToPin.endsWith(".apk");
+        int bytesPinned = 0;
+        PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin,
+                /*attemptPinIntrospection=*/isApk);
+        if (pf == null) {
+            Slog.e(TAG, "Failed to pin file = " + fileToPin);
+            return 0;
+        }
+        pf.groupName = groupName != null ? groupName : "";
+
+        maxBytesToPin -= bytesPinned;
+        bytesPinned += pf.bytesPinned;
+
+        synchronized (this) {
+            mPinnedFiles.put(pf.fileName, pf);
+        }
+        if (maxBytesToPin > 0) {
+            pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo);
+        }
+        return bytesPinned;
+    }
+
+    /**
+     * Pin any dependency optimized files generated by ART.
+     * @param pinnedFile An already pinned file whose dependencies we want pinned.
+     * @param maxBytesToPin Maximum amount of bytes to pin.
+     * @param appInfo Used to determine the ABI in case the application has one custom set, when set
+     *                to null it will use the default supported ABI by the device.
+     * @return total bytes pinned.
+     */
+    private int pinOptimizedDexDependencies(
+            PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) {
+        if (pinnedFile == null) {
+            return 0;
+        }
+
+        int bytesPinned = 0;
+        if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) {
+            String abi = null;
+            if (appInfo != null) {
+                abi = appInfo.primaryCpuAbi;
+            }
+            if (abi == null) {
+                abi = Build.SUPPORTED_ABIS[0];
+            }
+            // Check whether the runtime has compilation artifacts to pin.
+            String arch = VMRuntime.getInstructionSet(abi);
+            String[] files = null;
+            try {
+                files = DexFile.getDexFileOutputPaths(pinnedFile.fileName, arch);
+            } catch (IOException ioe) {
+            }
+            if (files == null) {
+                return bytesPinned;
+            }
+            for (String file : files) {
+                // Unpin if it was already pinned prior to re-pinning.
+                unpinFile(file);
+
+                PinnedFile df = mInjector.pinFileInternal(file, Integer.MAX_VALUE,
+                        /*attemptPinIntrospection=*/false);
+                if (df == null) {
+                    Slog.i(TAG, "Failed to pin ART file = " + file);
+                    return bytesPinned;
+                }
+                df.groupName = pinnedFile.groupName;
+                pinnedFile.pinnedDeps.add(df);
+                maxBytesToPin -= df.bytesPinned;
+                bytesPinned += df.bytesPinned;
+                synchronized (this) {
+                    mPinnedFiles.put(df.fileName, df);
                 }
             }
         }
+        return bytesPinned;
     }
 
     /** mlock length bytes of fileToPin in memory
@@ -950,9 +1039,12 @@
      *   zip in order to extract the
      * @return Pinned memory resource owner thing or null on error
      */
-    private static PinnedFile pinFile(String fileToPin,
-                                      int maxBytesToPin,
-                                      boolean attemptPinIntrospection) {
+    private static PinnedFile pinFileInternal(
+            String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) {
+        if (DEBUG) {
+            Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection);
+        }
+        Trace.beginSection("pinFile:" + fileToPin);
         ZipFile fileAsZip = null;
         InputStream pinRangeStream = null;
         try {
@@ -963,16 +1055,19 @@
             if (fileAsZip != null) {
                 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
             }
-
-            Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
-
-            PinRangeSource pinRangeSource = (pinRangeStream != null)
-                ? new PinRangeSourceStream(pinRangeStream)
-                : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
-            return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
+            boolean use_pinlist = (pinRangeStream != null);
+            PinRangeSource pinRangeSource = use_pinlist
+                    ? new PinRangeSourceStream(pinRangeStream)
+                    : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
+            PinnedFile pinnedFile = pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
+            if (pinnedFile != null) {
+                pinnedFile.used_pinlist = use_pinlist;
+            }
+            return pinnedFile;
         } finally {
             safeClose(pinRangeStream);
             safeClose(fileAsZip);  // Also closes any streams we've opened
+            Trace.endSection();
         }
     }
 
@@ -1008,9 +1103,23 @@
             return null;
         }
 
+        // Looking at root directory is the old behavior but still some apps rely on it so keeping
+        // for backward compatibility. As doing a single item lookup is cheap in the root.
         ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
+
+        if (pinMetaEntry == null) {
+            // It is usually within an apk's control to include files in assets/ directory
+            // so this would be the expected point to have the pinlist.meta coming from.
+            // we explicitly avoid doing an exhaustive search because it may be expensive so
+            // prefer to have a good known location to retrieve the file.
+            pinMetaEntry = zipFile.getEntry("assets/" + PIN_META_FILENAME);
+        }
+
         InputStream pinMetaStream = null;
         if (pinMetaEntry != null) {
+            if (DEBUG) {
+                Slog.d(TAG, "Found pinlist.meta for " + fileName);
+            }
             try {
                 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
             } catch (IOException ex) {
@@ -1019,6 +1128,10 @@
                                      fileName),
                        ex);
             }
+        } else {
+            Slog.w(TAG,
+                    String.format(
+                            "Could not find pinlist.meta for \"%s\": pinning as blob", fileName));
         }
         return pinMetaStream;
     }
@@ -1155,6 +1268,49 @@
             }
         }
     }
+    private List<PinnedFile> getAllPinsForGroup(String group) {
+        List<PinnedFile> filesInGroup;
+        synchronized (this) {
+            filesInGroup = mPinnedFiles.values()
+                                   .stream()
+                                   .filter(pf -> pf.groupName.equals(group))
+                                   .toList();
+        }
+        return filesInGroup;
+    }
+    public void unpinGroup(String group) {
+        List<PinnedFile> pinnedFiles = getAllPinsForGroup(group);
+        for (PinnedFile pf : pinnedFiles) {
+            unpinFile(pf.fileName);
+        }
+    }
+
+    public void unpinFile(String filename) {
+        PinnedFile pinnedFile;
+        synchronized (this) {
+            pinnedFile = mPinnedFiles.get(filename);
+        }
+        if (pinnedFile == null) {
+            // File not pinned, nothing to do.
+            return;
+        }
+        pinnedFile.close();
+        synchronized (this) {
+            if (DEBUG) {
+                Slog.d(TAG, "Unpinned file: " + filename);
+            }
+            mPinnedFiles.remove(pinnedFile.fileName);
+            for (PinnedFile dep : pinnedFile.pinnedDeps) {
+                if (dep == null) {
+                    continue;
+                }
+                mPinnedFiles.remove(dep.fileName);
+                if (DEBUG) {
+                    Slog.d(TAG, "Unpinned dependency: " + dep.fileName);
+                }
+            }
+        }
+    }
 
     private static int clamp(int min, int value, int max) {
         return Math.max(min, Math.min(value, max));
@@ -1200,17 +1356,44 @@
         }
     }
 
-    private final class BinderService extends Binder {
+    public List<PinnedFileStat> getPinnerStats() {
+        ArrayList<PinnedFileStat> stats = new ArrayList<>();
+        Collection<PinnedApp> pinnedApps;
+        synchronized(this) {
+            pinnedApps = mPinnedApps.values();
+        }
+        for (PinnedApp pinnedApp : pinnedApps) {
+            for (PinnedFile pf : pinnedApp.mFiles) {
+                PinnedFileStat stat =
+                        new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName);
+                stats.add(stat);
+            }
+        }
+
+        Collection<PinnedFile> pinnedFiles;
+        synchronized(this) {
+            pinnedFiles = mPinnedFiles.values();
+        }
+        for (PinnedFile pf : pinnedFiles) {
+            PinnedFileStat stat = new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName);
+            stats.add(stat);
+        }
+        if (mCurrentlyPinnedAnonSize > 0) {
+            stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME,
+                        mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
+        }
+        return stats;
+    }
+
+    public final class BinderService extends IPinnerService.Stub {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+            HashSet<PinnedFile> shownPins = new HashSet<>();
+            HashSet<String> groups = new HashSet<>();
+            final int bytesPerMB = 1024 * 1024;
             synchronized (PinnerService.this) {
                 long totalSize = 0;
-                for (PinnedFile pinnedFile : mPinnedFiles) {
-                    pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
-                    totalSize += pinnedFile.bytesPinned;
-                }
-                pw.println();
                 for (int key : mPinnedApps.keySet()) {
                     PinnedApp app = mPinnedApps.get(key);
                     pw.print(getNameForKey(key));
@@ -1218,14 +1401,53 @@
                     pw.print(" active="); pw.print(app.active);
                     pw.println();
                     for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
-                        pw.print("  "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
+                        pw.print("  ");
+                        pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName,
+                                pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist);
                         totalSize += pf.bytesPinned;
+                        shownPins.add(pf);
+                        for (PinnedFile dep : pf.pinnedDeps) {
+                            pw.print("  ");
+                            pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName,
+                                    dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist);
+                            totalSize += dep.bytesPinned;
+                            shownPins.add(dep);
+                        }
                     }
                 }
-                if (mPinAnonAddress != 0) {
-                    pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize);
+                pw.println();
+                for (PinnedFile pinnedFile : mPinnedFiles.values()) {
+                    if (!groups.contains(pinnedFile.groupName)) {
+                        groups.add(pinnedFile.groupName);
+                    }
                 }
-                pw.format("Total size: %s\n", totalSize);
+                boolean firstPinInGroup = true;
+                for (String group : groups) {
+                    List<PinnedFile> groupPins = getAllPinsForGroup(group);
+                    for (PinnedFile pinnedFile : groupPins) {
+                        if (shownPins.contains(pinnedFile)) {
+                            // Already showed in the dump and accounted for, skip.
+                            continue;
+                        }
+                        if (firstPinInGroup) {
+                            firstPinInGroup = false;
+                            // Ensure we only print when there are pins for groups not yet shown
+                            // in the pinned app section.
+                            pw.print("Group:" + group);
+                            pw.println();
+                        }
+                        pw.format("  %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName,
+                                pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB,
+                                pinnedFile.used_pinlist);
+                        totalSize += pinnedFile.bytesPinned;
+                    }
+                }
+                pw.println();
+                if (mPinAnonAddress != 0) {
+                    pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB);
+                    totalSize += mCurrentlyPinnedAnonSize;
+                }
+                pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB);
                 pw.println();
                 if (!mPendingRepin.isEmpty()) {
                     pw.print("Pending repin: ");
@@ -1272,14 +1494,29 @@
 
             resultReceiver.send(0, null);
         }
+
+        @EnforcePermission(android.Manifest.permission.DUMP)
+        @Override
+        public List<PinnedFileStat> getPinnerStats() {
+            getPinnerStats_enforcePermission();
+            return PinnerService.this.getPinnerStats();
+        }
     }
 
-    private static final class PinnedFile implements AutoCloseable {
+    @VisibleForTesting
+    public static final class PinnedFile implements AutoCloseable {
         private long mAddress;
         final int mapSize;
         final String fileName;
         final int bytesPinned;
 
+        // Whether this file was pinned using a pinlist
+        boolean used_pinlist;
+
+        // User defined group name for pinner accounting
+        String groupName = "";
+        ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
+
         PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
              mAddress = address;
              this.mapSize = mapSize;
@@ -1293,6 +1530,11 @@
                 safeMunmap(mAddress, mapSize);
                 mAddress = -1;
             }
+            for (PinnedFile dep : pinnedDeps) {
+                if (dep != null) {
+                    dep.close();
+                }
+            }
         }
 
         @Override
@@ -1350,5 +1592,4 @@
             }
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f6835fe..39b8643 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -215,7 +215,7 @@
     public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
 
     /** Extended timeout for the system server watchdog. */
-    private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
+    private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 20 * 1000;
 
     /** Extended timeout for the system server watchdog for vold#partition operation. */
     private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
@@ -1235,11 +1235,16 @@
         }
     }
 
+    private void extendWatchdogTimeout(String reason) {
+        Watchdog w = Watchdog.getInstance();
+        w.pauseWatchingMonitorsFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason);
+        w.pauseWatchingCurrentThreadFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason);
+    }
+
     private void onUserStopped(int userId) {
         Slog.d(TAG, "onUserStopped " + userId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
+        extendWatchdogTimeout("#onUserStopped might be slow");
         try {
             mVold.onUserStopped(userId);
             mStoraged.onUserStopped(userId);
@@ -1322,8 +1327,7 @@
                 unlockedUsers.add(userId);
             }
         }
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
+        extendWatchdogTimeout("#onUserStopped might be slow");
         for (Integer userId : unlockedUsers) {
             try {
                 mVold.onUserStopped(userId);
@@ -2343,8 +2347,7 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
+            extendWatchdogTimeout("#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
                 public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2474,8 +2477,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2493,8 +2495,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2512,8 +2513,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -3622,8 +3622,7 @@
 
         @Override
         public ParcelFileDescriptor open() throws AppFuseMountException {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
+            extendWatchdogTimeout("#open might be slow");
             try {
                 final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
                 mMounted = true;
@@ -3636,8 +3635,7 @@
         @Override
         public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
                 throws AppFuseMountException {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
+            extendWatchdogTimeout("#openFile might be slow");
             try {
                 return new ParcelFileDescriptor(
                         mVold.openAppFuseFile(uid, mountId, fileId, flags));
@@ -3648,8 +3646,7 @@
 
         @Override
         public void close() throws Exception {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
+            extendWatchdogTimeout("#close might be slow");
             if (mMounted) {
                 BackgroundThread.getHandler().post(() -> {
                     try {
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 80c4c58..708da19 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -71,6 +71,18 @@
             "file_patterns": ["BinaryTransparencyService\\.java"]
         },
         {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.PinnerServiceTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                }
+            ],
+            "file_patterns": ["PinnerService\\.java"]
+        },
+        {
             "name": "BinaryTransparencyHostTest",
             "file_patterns": ["BinaryTransparencyService\\.java"]
         },
@@ -139,6 +151,18 @@
         },
         {
             "name": "CtsSuspendAppsTestCases"
+        },
+        {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.PinnerServiceTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                }
+            ],
+            "file_patterns": ["PinnerService\\.java"]
         }
     ]
 }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index c718d39..9eb35fd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2096,14 +2096,48 @@
     /**
      * Send a notification to registrants about the data activity state.
      *
+     * @param subId the subscriptionId for the data connection
+     * @param state indicates the latest data activity type
+     * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN}
+     *
+     */
+
+    public void notifyDataActivityForSubscriber(int subId, int state) {
+        if (!checkNotifyPermission("notifyDataActivity()")) {
+            return;
+        }
+        int phoneId = getPhoneIdFromSubId(subId);
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mDataActivity[phoneId] = state;
+                for (Record r : mRecords) {
+                    // Notify by correct subId.
+                    if (r.matchTelephonyCallbackEvent(
+                            TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)
+                            && idMatch(r, subId, phoneId)) {
+                        try {
+                            r.callback.onDataActivity(state);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    /**
+     * Send a notification to registrants about the data activity state.
+     *
      * @param phoneId the phoneId carrying the data connection
      * @param subId the subscriptionId for the data connection
      * @param state indicates the latest data activity type
      * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN}
      *
      */
-    public void notifyDataActivityForSubscriber(int phoneId, int subId, int state) {
-        if (!checkNotifyPermission("notifyDataActivity()" )) {
+    public void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state) {
+        if (!checkNotifyPermission("notifyDataActivityWithSlot()")) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 0d423d8..2ba3a1d 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -33,7 +33,6 @@
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.IVpnManager;
-import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkStack;
 import android.net.UnderlyingNetworkInfo;
@@ -437,16 +436,9 @@
             throw new UnsupportedOperationException("Legacy VPN is deprecated");
         }
         int user = UserHandle.getUserId(mDeps.getCallingUid());
-        // Note that if the caller is not system (uid >= Process.FIRST_APPLICATION_UID),
-        // the code might not work well since getActiveNetwork might return null if the uid is
-        // blocked by NetworkPolicyManagerService.
-        final LinkProperties egress = mCm.getLinkProperties(mCm.getActiveNetwork());
-        if (egress == null) {
-            throw new IllegalStateException("Missing active network connection");
-        }
         synchronized (mVpns) {
             throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress);
+            mVpns.get(user).startLegacyVpn(profile);
         }
     }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 003046a..3135650 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -98,10 +98,16 @@
     //         applications may not work with a debug build. CTS will fail.
     private static final long DEFAULT_TIMEOUT = DB ? 10 * 1000 : 60 * 1000;
 
+    // This ratio is used to compute the pre-watchdog timeout (2 means that the pre-watchdog timeout
+    // will be half the full timeout).
+    //
+    // The pre-watchdog event is similar to a full watchdog except it does not crash system server.
+    private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 2;
+
     // These are temporally ordered: larger values as lateness increases
     private static final int COMPLETED = 0;
     private static final int WAITING = 1;
-    private static final int WAITED_HALF = 2;
+    private static final int WAITED_UNTIL_PRE_WATCHDOG = 2;
     private static final int OVERDUE = 3;
 
     // Track watchdog timeout history and break the crash loop if there is.
@@ -310,10 +316,10 @@
                 return COMPLETED;
             } else {
                 long latency = SystemClock.uptimeMillis() - mStartTimeMillis;
-                if (latency < mWaitMaxMillis / 2) {
+                if (latency < mWaitMaxMillis / PRE_WATCHDOG_TIMEOUT_RATIO) {
                     return WAITING;
                 } else if (latency < mWaitMaxMillis) {
-                    return WAITED_HALF;
+                    return WAITED_UNTIL_PRE_WATCHDOG;
                 }
             }
             return OVERDUE;
@@ -368,8 +374,9 @@
         public void pauseForLocked(int pauseMillis, String reason) {
             mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
             // Mark as completed, because there's a chance we called this after the watchog
-            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
-            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we
+            // want to ensure the next call to #getCompletionStateLocked for this checker returns
+            // 'COMPLETED'
             mCompleted = true;
             Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
                     + reason + ". Pause end time: " + mPauseEndTimeMillis);
@@ -379,8 +386,9 @@
         public void pauseLocked(String reason) {
             mPauseCount++;
             // Mark as completed, because there's a chance we called this after the watchog
-            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
-            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we
+            // want to ensure the next call to #getCompletionStateLocked for this checker returns
+            // 'COMPLETED'
             mCompleted = true;
             Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
                     + reason + ". Pause count: " + mPauseCount);
@@ -797,11 +805,11 @@
             String subject = "";
             boolean allowRestart = true;
             int debuggerWasConnected = 0;
-            boolean doWaitedHalfDump = false;
+            boolean doWaitedPreDump = false;
             // The value of mWatchdogTimeoutMillis might change while we are executing the loop.
             // We store the current value to use a consistent value for all handlers.
             final long watchdogTimeoutMillis = mWatchdogTimeoutMillis;
-            final long checkIntervalMillis = watchdogTimeoutMillis / 2;
+            final long checkIntervalMillis = watchdogTimeoutMillis / PRE_WATCHDOG_TIMEOUT_RATIO;
             final ArrayList<Integer> pids;
             synchronized (mLock) {
                 long timeout = checkIntervalMillis;
@@ -848,15 +856,16 @@
                 } else if (waitState == WAITING) {
                     // still waiting but within their configured intervals; back off and recheck
                     continue;
-                } else if (waitState == WAITED_HALF) {
+                } else if (waitState == WAITED_UNTIL_PRE_WATCHDOG) {
                     if (!waitedHalf) {
-                        Slog.i(TAG, "WAITED_HALF");
+                        Slog.i(TAG, "WAITED_UNTIL_PRE_WATCHDOG");
                         waitedHalf = true;
-                        // We've waited half, but we'd need to do the stack trace dump w/o the lock.
-                        blockedCheckers = getCheckersWithStateLocked(WAITED_HALF);
+                        // We've waited until the pre-watchdog, but we'd need to do the stack trace
+                        // dump w/o the lock.
+                        blockedCheckers = getCheckersWithStateLocked(WAITED_UNTIL_PRE_WATCHDOG);
                         subject = describeCheckersLocked(blockedCheckers);
                         pids = new ArrayList<>(mInterestingJavaPids);
-                        doWaitedHalfDump = true;
+                        doWaitedPreDump = true;
                     } else {
                         continue;
                     }
@@ -874,12 +883,12 @@
             // First collect stack traces from all threads of the system process.
             //
             // Then, if we reached the full timeout, kill this process so that the system will
-            // restart. If we reached half of the timeout, just log some information and continue.
-            logWatchog(doWaitedHalfDump, subject, pids);
+            // restart. If we reached pre-watchdog timeout, just log some information and continue.
+            logWatchog(doWaitedPreDump, subject, pids);
 
-            if (doWaitedHalfDump) {
-                // We have waited for only half of the timeout, we continue to wait for the duration
-                // of the full timeout before killing the process.
+            if (doWaitedPreDump) {
+                // We have waited for only pre-watchdog timeout, we continue to wait for the
+                // duration of the full timeout before killing the process.
                 continue;
             }
 
@@ -928,8 +937,8 @@
         }
     }
 
-    private void logWatchog(boolean halfWatchdog, String subject, ArrayList<Integer> pids) {
-        // Get critical event log before logging the half watchdog so that it doesn't
+    private void logWatchog(boolean preWatchdog, String subject, ArrayList<Integer> pids) {
+        // Get critical event log before logging the pre-watchdog so that it doesn't
         // occur in the log.
         String criticalEvents =
                 CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
@@ -941,7 +950,7 @@
         }
 
         final String dropboxTag;
-        if (halfWatchdog) {
+        if (preWatchdog) {
             dropboxTag = "pre_watchdog";
             CriticalEventLog.getInstance().logHalfWatchdog(subject);
             FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED);
@@ -971,7 +980,7 @@
         report.append(processCpuTracker.printCurrentState(anrTime, 10));
         report.append(tracesFileException.getBuffer());
 
-        if (!halfWatchdog) {
+        if (!preWatchdog) {
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the
             // kernel log
             doSysRq('w');
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9716cf6..3ce91c8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1076,6 +1076,16 @@
 
     public boolean APP_PROFILER_PSS_PROFILING_DISABLED;
 
+    /**
+     * The modifier used to adjust PSS thresholds in OomAdjuster when RSS is collected instead.
+     */
+    static final String KEY_PSS_TO_RSS_THRESHOLD_MODIFIER =
+            "pss_to_rss_threshold_modifier";
+
+    private final float mDefaultPssToRssThresholdModifier;
+
+    public float PSS_TO_RSS_THRESHOLD_MODIFIER;
+
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -1254,6 +1264,9 @@
                             case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
                                 updateDisableAppProfilerPssProfiling();
                                 break;
+                            case KEY_PSS_TO_RSS_THRESHOLD_MODIFIER:
+                                updatePssToRssThresholdModifier();
+                                break;
                             default:
                                 updateFGSPermissionEnforcementFlagsIfNecessary(name);
                                 break;
@@ -1339,6 +1352,10 @@
         mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean(
                 R.bool.config_am_disablePssProfiling);
         APP_PROFILER_PSS_PROFILING_DISABLED = mDefaultDisableAppProfilerPssProfiling;
+
+        mDefaultPssToRssThresholdModifier = context.getResources().getFloat(
+                com.android.internal.R.dimen.config_am_pssToRssThresholdModifier);
+        PSS_TO_RSS_THRESHOLD_MODIFIER = mDefaultPssToRssThresholdModifier;
     }
 
     public void start(ContentResolver resolver) {
@@ -2051,6 +2068,12 @@
                 mDefaultDisableAppProfilerPssProfiling);
     }
 
+    private void updatePssToRssThresholdModifier() {
+        PSS_TO_RSS_THRESHOLD_MODIFIER = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_PSS_TO_RSS_THRESHOLD_MODIFIER,
+                mDefaultPssToRssThresholdModifier);
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     void dump(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2242,6 +2265,9 @@
         pw.print("  "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING);
         pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED);
 
+        pw.print("  "); pw.print(KEY_PSS_TO_RSS_THRESHOLD_MODIFIER);
+        pw.print("="); pw.println(PSS_TO_RSS_THRESHOLD_MODIFIER);
+
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
             pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 5dd0a3f..55b161a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -71,6 +71,7 @@
     static final boolean DEBUG_PROCESSES = DEBUG_ALL || false;
     static final boolean DEBUG_PROVIDER = DEBUG_ALL || false;
     static final boolean DEBUG_PSS = DEBUG_ALL || false;
+    static final boolean DEBUG_RSS = DEBUG_ALL || false;
     static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
@@ -91,6 +92,7 @@
             ? "_ProcessObservers" : "";
     static final String POSTFIX_PROCESSES = (APPEND_CATEGORY_NAME) ? "_Processes" : "";
     static final String POSTFIX_PSS = (APPEND_CATEGORY_NAME) ? "_Pss" : "";
+    static final String POSTFIX_RSS = (APPEND_CATEGORY_NAME) ? "_Rss" : "";
     static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : "";
     static final String POSTFIX_SERVICE_EXECUTING =
             (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b2a7948..54c8ed3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -327,6 +327,7 @@
 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;
@@ -4851,8 +4852,10 @@
         } else {
             Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
                     + ". Uid: " + uid);
-            killProcess(pid);
-            killProcessGroup(uid, pid);
+            if (pid > 0) {
+                killProcess(pid);
+                killProcessGroup(uid, pid);
+            }
             mProcessList.noteAppKill(pid, uid,
                     ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
                     ApplicationExitInfo.SUBREASON_UNKNOWN,
@@ -6563,7 +6566,7 @@
                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
 
             final Intent intent = new Intent();
-            intent.setData(uri);
+            intent.setData(ContentProvider.maybeAddUserId(uri, userId));
             intent.setFlags(modeFlags);
 
             final NeededUriGrants needed = mUgmInternal.checkGrantUriPermissionFromIntent(intent,
@@ -8563,6 +8566,12 @@
             // If the processes' memory has increased by more than 1% of the total memory,
             // or 10 MB, whichever is greater, then the processes' are eligible to be killed.
             final long totalMemoryInKb = getTotalMemory() / 1000;
+
+            // This threshold should be applicable to both PSS and RSS because the value is absolute
+            // and represents an increase in process memory relative to its own previous state.
+            //
+            // TODO(b/296454553): Tune this value during the flag rollout process if more processes
+            // seem to be getting killed than before.
             final long memoryGrowthThreshold =
                     Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD);
             mProcessList.forEachLruProcessesLOSP(false, proc -> {
@@ -8575,24 +8584,34 @@
                 if (state.isNotCachedSinceIdle()) {
                     if (setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                             && setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
-                        final long initialIdlePss, lastPss, lastSwapPss;
+                        final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss;
                         synchronized (mAppProfiler.mProfilerLock) {
-                            initialIdlePss = pr.getInitialIdlePss();
-                            lastPss = pr.getLastPss();
+                            initialIdlePssOrRss = pr.getInitialIdlePssOrRss();
+                            lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+                                    ? pr.getLastPss() : pr.getLastRss();
                             lastSwapPss = pr.getLastSwapPss();
                         }
-                        if (doKilling && initialIdlePss != 0
-                                && lastPss > (initialIdlePss * 3 / 2)
-                                && lastPss > (initialIdlePss + memoryGrowthThreshold)) {
+                        if (doKilling && initialIdlePssOrRss != 0
+                                && lastPssOrRss > (initialIdlePssOrRss * 3 / 2)
+                                && lastPssOrRss > (initialIdlePssOrRss + memoryGrowthThreshold)) {
                             final StringBuilder sb2 = new StringBuilder(128);
                             sb2.append("Kill");
                             sb2.append(proc.processName);
-                            sb2.append(" in idle maint: pss=");
-                            sb2.append(lastPss);
-                            sb2.append(", swapPss=");
-                            sb2.append(lastSwapPss);
-                            sb2.append(", initialPss=");
-                            sb2.append(initialIdlePss);
+                            if (!Flags.removeAppProfilerPssCollection()) {
+                                sb2.append(" in idle maint: pss=");
+                            } else {
+                                sb2.append(" in idle maint: rss=");
+                            }
+                            sb2.append(lastPssOrRss);
+
+                            if (!Flags.removeAppProfilerPssCollection()) {
+                                sb2.append(", swapPss=");
+                                sb2.append(lastSwapPss);
+                                sb2.append(", initialPss=");
+                            } else {
+                                sb2.append(", initialRss=");
+                            }
+                            sb2.append(initialIdlePssOrRss);
                             sb2.append(", period=");
                             TimeUtils.formatDuration(timeSinceLastIdle, sb2);
                             sb2.append(", lowRamPeriod=");
@@ -8600,8 +8619,9 @@
                             Slog.wtfQuiet(TAG, sb2.toString());
                             mHandler.post(() -> {
                                 synchronized (ActivityManagerService.this) {
-                                    proc.killLocked("idle maint (pss " + lastPss
-                                            + " from " + initialIdlePss + ")",
+                                    proc.killLocked(!Flags.removeAppProfilerPssCollection()
+                                            ? "idle maint (pss " : "idle maint (rss " + lastPssOrRss
+                                            + " from " + initialIdlePssOrRss + ")",
                                             ApplicationExitInfo.REASON_OTHER,
                                             ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
                                             true);
@@ -8613,7 +8633,7 @@
                         && setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) {
                     state.setNotCachedSinceIdle(true);
                     synchronized (mAppProfiler.mProfilerLock) {
-                        pr.setInitialIdlePss(0);
+                        pr.setInitialIdlePssOrRss(0);
                         mAppProfiler.updateNextPssTimeLPf(
                                 state.getSetProcState(), proc.mProfile, now, true);
                     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a95ddf3..f3b2ef3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -552,7 +552,8 @@
                     mAsync = true;
                 } else if (opt.equals("--splashscreen-show-icon")) {
                     mShowSplashScreen = true;
-                } else if (opt.equals("--dismiss-keyguard-if-insecure")) {
+                } else if (opt.equals("--dismiss-keyguard-if-insecure")
+                      || opt.equals("--dismiss-keyguard")) {
                     mDismissKeyguardIfInsecure = true;
                 } else {
                     return false;
@@ -4157,7 +4158,7 @@
             pw.println("      -D: enable debugging");
             pw.println("      --suspend: debugged app suspend threads at startup (only with -D)");
             pw.println("      -N: enable native debugging");
-            pw.println("      -W: wait for launch to complete");
+            pw.println("      -W: wait for launch to complete (initial display)");
             pw.println("      --start-profiler <FILE>: start profiler and send results to <FILE>");
             pw.println("      --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
             pw.println("          between samples (use with --start-profiler)");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3cf4332..2e0aec9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -28,7 +28,9 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RSS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RSS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_ADJ;
@@ -64,6 +66,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Debug;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -125,6 +128,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
     static final String TAG_PSS = TAG + POSTFIX_PSS;
+    static final String TAG_RSS = TAG + POSTFIX_RSS;
 
     static final String TAG_OOM_ADJ = ActivityManagerService.TAG_OOM_ADJ;
 
@@ -183,10 +187,10 @@
     private volatile long mPssDeferralTime = 0;
 
     /**
-     * Processes we want to collect PSS data from.
+     * Processes we want to collect PSS or RSS data from.
      */
     @GuardedBy("mProfilerLock")
-    private final ArrayList<ProcessProfileRecord> mPendingPssProfiles = new ArrayList<>();
+    private final ArrayList<ProcessProfileRecord> mPendingPssOrRssProfiles = new ArrayList<>();
 
     /**
      * Depth of overlapping activity-start PSS deferral notes
@@ -200,18 +204,18 @@
     private long mLastFullPssTime = SystemClock.uptimeMillis();
 
     /**
-     * If set, the next time we collect PSS data we should do a full collection
-     * with data from native processes and the kernel.
+     * If set, the next time we collect PSS or RSS data we should do a full collection with data
+     * from native processes and the kernel.
      */
     @GuardedBy("mProfilerLock")
-    private boolean mFullPssPending = false;
+    private boolean mFullPssOrRssPending = false;
 
     /**
-     * If true, we are running under a test environment so will sample PSS from processes
-     * much more rapidly to try to collect better data when the tests are rapidly
-     * running through apps.
+     * If true, we are running under a test environment so will sample PSS or RSS from processes
+     * much more rapidly to try to collect better data when the tests are rapidly running through
+     * apps.
      */
-    private volatile boolean mTestPssMode = false;
+    private volatile boolean mTestPssOrRssMode = false;
 
     private final LowMemDetector mLowMemDetector;
 
@@ -598,7 +602,11 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case COLLECT_PSS_BG_MSG:
-                    collectPssInBackground();
+                    if (!Flags.removeAppProfilerPssCollection()) {
+                        collectPssInBackground();
+                    } else {
+                        collectRssInBackground();
+                    }
                     break;
                 case DEFER_PSS_MSG:
                     deferPssForActivityStart();
@@ -619,8 +627,8 @@
         long start = SystemClock.uptimeMillis();
         MemInfoReader memInfo = null;
         synchronized (mProfilerLock) {
-            if (mFullPssPending) {
-                mFullPssPending = false;
+            if (mFullPssOrRssPending) {
+                mFullPssOrRssPending = false;
                 memInfo = new MemInfoReader();
             }
         }
@@ -673,16 +681,16 @@
             int pid = -1;
             long lastPssTime;
             synchronized (mProfilerLock) {
-                if (mPendingPssProfiles.size() <= 0) {
-                    if (mTestPssMode || DEBUG_PSS) {
+                if (mPendingPssOrRssProfiles.size() <= 0) {
+                    if (mTestPssOrRssMode || DEBUG_PSS) {
                         Slog.d(TAG_PSS,
                                 "Collected pss of " + num + " processes in "
                                 + (SystemClock.uptimeMillis() - start) + "ms");
                     }
-                    mPendingPssProfiles.clear();
+                    mPendingPssOrRssProfiles.clear();
                     return;
                 }
-                profile = mPendingPssProfiles.remove(0);
+                profile = mPendingPssOrRssProfiles.remove(0);
                 procState = profile.getPssProcState();
                 statType = profile.getPssStatType();
                 lastPssTime = profile.getLastPssTime();
@@ -740,13 +748,149 @@
         } while (true);
     }
 
+    // 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
+    // technically necessary. These can be updated once the flag is completely rolled out.
+    private void collectRssInBackground() {
+        long start = SystemClock.uptimeMillis();
+        MemInfoReader memInfo = null;
+        synchronized (mProfilerLock) {
+            if (mFullPssOrRssPending) {
+                mFullPssOrRssPending = false;
+                memInfo = new MemInfoReader();
+            }
+        }
+        if (memInfo != null) {
+            updateCpuStatsNow();
+            long nativeTotalRss = 0;
+            final List<ProcessCpuTracker.Stats> stats;
+            synchronized (mProcessCpuTracker) {
+                stats = mProcessCpuTracker.getStats(st -> {
+                    return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID;
+                });
+            }
+
+            // We assume that if PSS collection isn't needed or desired, RSS collection can be
+            // disabled as well.
+            if (!mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED) {
+                final int numOfStats = stats.size();
+                for (int j = 0; j < numOfStats; j++) {
+                    synchronized (mService.mPidsSelfLocked) {
+                        if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) {
+                            // This is one of our own processes; skip it.
+                            continue;
+                        }
+                    }
+                    nativeTotalRss += Debug.getRss(stats.get(j).pid, null);
+                }
+            }
+
+            memInfo.readMemInfo();
+            synchronized (mService.mProcessStats.mLock) {
+                // We assume that an enabled DEBUG_PSS can apply to RSS as well, since only one of
+                // either collectPssInBackground() or collectRssInBackground() will be used.
+                if (DEBUG_RSS) {
+                    Slog.d(TAG_RSS, "Collected native and kernel memory in "
+                            + (SystemClock.uptimeMillis() - start) + "ms");
+                }
+                final long cachedKb = memInfo.getCachedSizeKb();
+                final long freeKb = memInfo.getFreeSizeKb();
+                final long zramKb = memInfo.getZramTotalSizeKb();
+                final long kernelKb = memInfo.getKernelUsedSizeKb();
+                // The last value needs to be updated in log tags to refer to RSS; this will be
+                // updated once the flag is fully rolled out.
+                EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024,
+                        kernelKb * 1024, nativeTotalRss * 1024);
+                mService.mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb,
+                        nativeTotalRss);
+            }
+        }
+
+        // This loop differs from its original form in collectPssInBackground(), as it does not
+        // collect USS or SwapPss (since those are reported in smaps, not status).
+        int num = 0;
+        do {
+            ProcessProfileRecord profile;
+            int procState;
+            int statType;
+            int pid = -1;
+            long lastRssTime;
+            synchronized (mProfilerLock) {
+                if (mPendingPssOrRssProfiles.size() <= 0) {
+                    if (mTestPssOrRssMode || DEBUG_RSS) {
+                        Slog.d(TAG_RSS,
+                                "Collected rss of " + num + " processes in "
+                                + (SystemClock.uptimeMillis() - start) + "ms");
+                    }
+                    mPendingPssOrRssProfiles.clear();
+                    return;
+                }
+                profile = mPendingPssOrRssProfiles.remove(0);
+                procState = profile.getPssProcState();
+                statType = profile.getPssStatType();
+                lastRssTime = profile.getLastPssTime();
+                long now = SystemClock.uptimeMillis();
+                if (profile.getThread() != null && procState == profile.getSetProcState()
+                        && (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) {
+                    pid = profile.getPid();
+                } else {
+                    profile.abortNextPssTime();
+                    if (DEBUG_RSS) {
+                        Slog.d(TAG_RSS, "Skipped rss collection of " + pid
+                                + ": still need "
+                                + (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now)
+                                + "ms until safe");
+                    }
+                    profile = null;
+                    pid = 0;
+                }
+            }
+            if (profile != null) {
+                long startTime = SystemClock.currentThreadTimeMillis();
+                // skip background RSS calculation under the following situations:
+                //  - app is capturing camera imagery
+                //  - app is frozen and we have already collected RSS once.
+                final boolean skipRSSCollection =
+                        (profile.mApp.mOptRecord != null
+                         && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen())
+                        || mService.isCameraActiveForUid(profile.mApp.uid)
+                        || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
+                long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null);
+                long endTime = SystemClock.currentThreadTimeMillis();
+                synchronized (mProfilerLock) {
+                    if (rss != 0 && profile.getThread() != null
+                            && profile.getSetProcState() == procState
+                            && profile.getPid() == pid && profile.getLastPssTime() == lastRssTime) {
+                        num++;
+                        profile.commitNextPssTime();
+                        recordRssSampleLPf(profile, procState, rss, statType, endTime - startTime,
+                                SystemClock.uptimeMillis());
+                    } else {
+                        profile.abortNextPssTime();
+                        if (DEBUG_RSS) {
+                            Slog.d(TAG_RSS, "Skipped rss collection of " + pid
+                                    + ": " + (profile.getThread() == null ? "NO_THREAD " : "")
+                                    + (skipRSSCollection ? "SKIP_RSS_COLLECTION " : "")
+                                    + (profile.getPid() != pid ? "PID_CHANGED " : "")
+                                    + " initState=" + procState + " curState="
+                                    + profile.getSetProcState() + " "
+                                    + (profile.getLastPssTime() != lastRssTime
+                                    ? "TIME_CHANGED" : ""));
+                        }
+                    }
+                }
+            }
+        } while (true);
+    }
+
     @GuardedBy("mProfilerLock")
     void updateNextPssTimeLPf(int procState, ProcessProfileRecord profile, long now,
             boolean forceUpdate) {
         if (!forceUpdate) {
             if (now <= profile.getNextPssTime() && now <= Math.max(profile.getLastPssTime()
                     + ProcessList.PSS_MAX_INTERVAL, profile.getLastStateTime()
-                    + ProcessList.minTimeFromStateChange(mTestPssMode))) {
+                    + ProcessList.minTimeFromStateChange(mTestPssOrRssMode))) {
                 // update is not due, ignore it.
                 return;
             }
@@ -755,7 +899,7 @@
             }
         }
         profile.setNextPssTime(profile.computeNextPssTime(procState,
-                mTestPssMode, mService.mAtmInternal.isSleeping(), now));
+                mTestPssOrRssMode, mService.mAtmInternal.isSleeping(), now));
     }
 
     /**
@@ -776,8 +920,8 @@
                     + " lastPss=" + profile.getLastPss()
                     + " state=" + ProcessList.makeProcStateString(procState));
         }
-        if (profile.getInitialIdlePss() == 0) {
-            profile.setInitialIdlePss(pss);
+        if (profile.getInitialIdlePssOrRss() == 0) {
+            profile.setInitialIdlePssOrRss(pss);
         }
         profile.setLastPss(pss);
         profile.setLastSwapPss(swapPss);
@@ -813,6 +957,72 @@
         }
     }
 
+    /**
+     * Record new RSS sample for a process.
+     *
+     * This method is analogous to recordPssSampleLPf() and is intended to be used as a replacement
+     * if Flags.removeAppProfilerPssCollection() is enabled. Functionally, this differs in that PSS,
+     * SwapPss, and USS are no longer collected and reported.
+     *
+     * This method will also poll PSS if the app has requested that a heap dump be taken if its PSS
+     * reaches some threshold set with ActivityManager.setWatchHeapLimit().
+     */
+    @GuardedBy("mProfilerLock")
+    private void recordRssSampleLPf(ProcessProfileRecord profile, int procState, long rss,
+            int statType, long rssDuration, long now) {
+        final ProcessRecord proc = profile.mApp;
+        // TODO(b/296454553): writeAmPss needs to be renamed to writeAmRss, and the zeroed out
+        // fields need to be removed. This will be updated once the flag is fully rolled out to
+        // avoid churn in the .logtags file, which has a mapping of IDs to tags (and is also
+        // technically deprecated).
+        EventLogTags.writeAmPss(
+                profile.getPid(), proc.uid, proc.processName, /* pss = */ 0, /* uss = */ 0,
+                /* swapPss = */ 0, rss * 1024, statType, procState, rssDuration);
+        profile.setLastPssTime(now);
+        // The PSS here is emitted in logs, so we can zero it out instead of subbing in RSS.
+        profile.addPss(/* pss = */ 0, /* uss = */ 0, rss, true, statType, rssDuration);
+        if (DEBUG_RSS) {
+            Slog.d(TAG_RSS,
+                    "rss of " + proc.toShortString() + ": " + rss
+                    + " lastRss=" + profile.getLastRss()
+                    + " state=" + ProcessList.makeProcStateString(procState));
+        }
+        if (profile.getInitialIdlePssOrRss() == 0) {
+            profile.setInitialIdlePssOrRss(rss);
+        }
+        profile.setLastRss(rss);
+        if (procState >= ActivityManager.PROCESS_STATE_HOME) {
+            profile.setLastCachedRss(rss);
+        }
+
+        final SparseArray<Pair<Long, String>> watchUids =
+                mMemWatchProcesses.getMap().get(proc.processName);
+        Long check = null;
+        if (watchUids != null) {
+            Pair<Long, String> val = watchUids.get(proc.uid);
+            if (val == null) {
+                val = watchUids.get(0);
+            }
+            if (val != null) {
+                check = val.first;
+            }
+        }
+
+        if (check != null) {
+            long pss = Debug.getPss(profile.getPid(), null, null);
+            if ((pss * 1024) >= check && profile.getThread() != null
+                    && mMemWatchDumpProcName == null) {
+                if (Build.IS_DEBUGGABLE || proc.isDebuggable()) {
+                    Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting");
+                    startHeapDumpLPf(profile, false);
+                } else {
+                    Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check
+                            + ", but debugging not enabled");
+                }
+            }
+        }
+    }
+
     private final class RecordPssRunnable implements Runnable {
         private final ProcessProfileRecord mProfile;
         private final Uri mDumpUri;
@@ -984,10 +1194,10 @@
      */
     @GuardedBy("mProfilerLock")
     private boolean requestPssLPf(ProcessProfileRecord profile, int procState) {
-        if (mPendingPssProfiles.contains(profile)) {
+        if (mPendingPssOrRssProfiles.contains(profile)) {
             return false;
         }
-        if (mPendingPssProfiles.size() == 0) {
+        if (mPendingPssOrRssProfiles.size() == 0) {
             final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0)
                     ? mPssDeferralTime : 0;
             if (DEBUG_PSS && deferral > 0) {
@@ -999,7 +1209,7 @@
         if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + profile.mApp);
         profile.setPssProcState(procState);
         profile.setPssStatType(ProcessStats.ADD_PSS_INTERNAL_SINGLE);
-        mPendingPssProfiles.add(profile);
+        mPendingPssOrRssProfiles.add(profile);
         return true;
     }
 
@@ -1009,7 +1219,7 @@
      */
     @GuardedBy("mProfilerLock")
     private void deferPssIfNeededLPf() {
-        if (mPendingPssProfiles.size() > 0) {
+        if (mPendingPssOrRssProfiles.size() > 0) {
             mBgHandler.removeMessages(BgHandler.COLLECT_PSS_BG_MSG);
             mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, mPssDeferralTime);
         }
@@ -1063,12 +1273,12 @@
                 Slog.d(TAG_PSS, "Requesting pss of all procs!  memLowered=" + memLowered);
             }
             mLastFullPssTime = now;
-            mFullPssPending = true;
-            for (int i = mPendingPssProfiles.size() - 1; i >= 0; i--) {
-                mPendingPssProfiles.get(i).abortNextPssTime();
+            mFullPssOrRssPending = true;
+            for (int i = mPendingPssOrRssProfiles.size() - 1; i >= 0; i--) {
+                mPendingPssOrRssProfiles.get(i).abortNextPssTime();
             }
-            mPendingPssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP());
-            mPendingPssProfiles.clear();
+            mPendingPssOrRssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP());
+            mPendingPssOrRssProfiles.clear();
             mService.mProcessList.forEachLruProcessesLOSP(false, app -> {
                 final ProcessProfileRecord profile = app.mProfile;
                 if (profile.getThread() == null
@@ -1083,7 +1293,7 @@
                     profile.setPssStatType(always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL
                             : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM);
                     updateNextPssTimeLPf(profile.getSetProcState(), profile, now, true);
-                    mPendingPssProfiles.add(profile);
+                    mPendingPssOrRssProfiles.add(profile);
                 }
             });
             if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) {
@@ -1094,7 +1304,7 @@
 
     void setTestPssMode(boolean enabled) {
         synchronized (mProcLock) {
-            mTestPssMode = enabled;
+            mTestPssOrRssMode = enabled;
             if (enabled) {
                 // Whenever we enable the mode, we want to take a snapshot all of current
                 // process mem use.
@@ -1104,7 +1314,7 @@
     }
 
     boolean getTestPssMode() {
-        return mTestPssMode;
+        return mTestPssOrRssMode;
     }
 
     @GuardedBy("mService")
@@ -2346,7 +2556,7 @@
         synchronized (mProfilerLock) {
             final ProcessProfileRecord profile = app.mProfile;
             mProcessesToGc.remove(app);
-            mPendingPssProfiles.remove(profile);
+            mPendingPssOrRssProfiles.remove(profile);
             profile.abortNextPssTime();
         }
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 36356bd..1928780 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -120,12 +120,15 @@
 import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.AggregatedPowerStatsConfig;
 import com.android.server.power.stats.BatteryExternalStatsWorker;
+import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
 import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor;
 import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsExporter;
 import com.android.server.power.stats.PowerStatsScheduler;
 import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.PowerStatsUidResolver;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.power.stats.wakeups.CpuWakeupStats;
 
@@ -181,6 +184,8 @@
     private final BatteryExternalStatsWorker mWorker;
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
     private final AtomicFile mConfigFile;
+    private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
+    private final PowerStatsUidResolver mPowerStatsUidResolver;
 
     private volatile boolean mMonitorEnabled = true;
 
@@ -408,9 +413,10 @@
                         .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
                         .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
                         .build();
+        mPowerStatsUidResolver = new PowerStatsUidResolver();
         mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                 systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
-                mCpuScalingPolicies);
+                mCpuScalingPolicies, mPowerStatsUidResolver);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
@@ -419,8 +425,6 @@
 
         AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
-                mPowerStatsStore);
         mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
                 mStats.getHistory());
         final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
@@ -429,7 +433,14 @@
                 com.android.internal.R.integer.config_powerStatsAggregationPeriod);
         mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
                 aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
-                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
+                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats);
+        PowerStatsExporter powerStatsExporter =
+                new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator);
+        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
+                powerStatsExporter, mPowerProfile, mCpuScalingPolicies,
+                mPowerStatsStore, Clock.SYSTEM_CLOCK);
+        mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore);
+        mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
         mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
     }
@@ -469,9 +480,10 @@
     }
 
     public void systemServicesReady() {
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats());
+        mWorker.systemServicesReady();
         mStats.systemServicesReady(mContext);
         mCpuWakeupStats.systemServicesReady();
-        mWorker.systemServicesReady();
         final INetworkManagementService nms = INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
         final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
@@ -775,25 +787,15 @@
     }
 
     void addIsolatedUid(final int isolatedUid, final int appUid) {
-        synchronized (mLock) {
-            final long elapsedRealtime = SystemClock.elapsedRealtime();
-            final long uptime = SystemClock.uptimeMillis();
-            mHandler.post(() -> {
-                synchronized (mStats) {
-                    mStats.addIsolatedUidLocked(isolatedUid, appUid, elapsedRealtime, uptime);
-                }
-            });
-        }
+        mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, appUid);
+        FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid,
+                FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
     }
 
     void removeIsolatedUid(final int isolatedUid, final int appUid) {
-        synchronized (mLock) {
-            mHandler.post(() -> {
-                synchronized (mStats) {
-                    mStats.scheduleRemoveIsolatedUidLocked(isolatedUid, appUid);
-                }
-            });
-        }
+        mPowerStatsUidResolver.noteIsolatedUidRemoved(isolatedUid, appUid);
+        FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, isolatedUid,
+                FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
     }
 
     void noteProcessStart(final String name, final int uid) {
@@ -877,12 +879,15 @@
 
         awaitCompletion();
 
-        if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
+        if (BatteryUsageStatsProvider.shouldUpdateStats(queries,
+                SystemClock.elapsedRealtime(),
                 mWorker.getLastCollectionTimeStamp())) {
             syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
         }
 
-        return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
+        synchronized (mStats) {
+            return mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, queries);
+        }
     }
 
     /** Register callbacks for statsd pulled atoms. */
@@ -2723,7 +2728,7 @@
         synchronized (mStats) {
             mStats.prepareForDumpLocked();
             BatteryUsageStats batteryUsageStats =
-                    mBatteryUsageStatsProvider.getBatteryUsageStats(query);
+                    mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query);
             if (proto) {
                 batteryUsageStats.dumpToProto(fd);
             } else {
@@ -3008,11 +3013,11 @@
                                         mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
-                                        mCpuScalingPolicies);
+                                        mCpuScalingPolicies, new PowerStatsUidResolver());
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
-                                checkinStats.dumpProtoLocked(
-                                        mContext, fd, apps, flags, historyStart);
+                                checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+                                        historyStart, mDumpHelper);
                                 mStats.mCheckinFile.delete();
                                 return;
                             }
@@ -3026,7 +3031,7 @@
             if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
             awaitCompletion();
             synchronized (mStats) {
-                mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+                mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart, mDumpHelper);
                 if (writeData) {
                     mStats.writeAsyncLocked();
                 }
@@ -3050,11 +3055,11 @@
                                         mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
-                                        mCpuScalingPolicies);
+                                        mCpuScalingPolicies, new PowerStatsUidResolver());
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckin(mContext, pw, apps, flags,
-                                        historyStart);
+                                        historyStart, mDumpHelper);
                                 mStats.mCheckinFile.delete();
                                 return;
                             }
@@ -3067,7 +3072,7 @@
             }
             if (DBG) Slog.d(TAG, "begin dumpCheckin from UID " + Binder.getCallingUid());
             awaitCompletion();
-            mStats.dumpCheckin(mContext, pw, apps, flags, historyStart);
+            mStats.dumpCheckin(mContext, pw, apps, flags, historyStart, mDumpHelper);
             if (writeData) {
                 mStats.writeAsyncLocked();
             }
@@ -3076,7 +3081,7 @@
             if (DBG) Slog.d(TAG, "begin dump from UID " + Binder.getCallingUid());
             awaitCompletion();
 
-            mStats.dump(mContext, pw, flags, reqUid, historyStart);
+            mStats.dump(mContext, pw, flags, reqUid, historyStart, mDumpHelper);
             if (writeData) {
                 mStats.writeAsyncLocked();
             }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5b54561..e07631c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -193,6 +193,12 @@
     private @Nullable BroadcastProcessQueue mRunningColdStart;
 
     /**
+     * Indicates whether we have queued a message to check pending cold start validity.
+     */
+    @GuardedBy("mService")
+    private boolean mCheckPendingColdStartQueued;
+
+    /**
      * Collection of latches waiting for device to reach specific state. The
      * first argument is a function to test for the desired state, and the
      * second argument is the latch to release once that state is reached.
@@ -302,7 +308,11 @@
                 return true;
             }
             case MSG_CHECK_PENDING_COLD_START_VALIDITY: {
-                checkPendingColdStartValidity();
+                synchronized (mService) {
+                    /* Clear this as we have just received the broadcast. */
+                    mCheckPendingColdStartQueued = false;
+                    checkPendingColdStartValidityLocked();
+                }
                 return true;
             }
             case MSG_PROCESS_FREEZABLE_CHANGED: {
@@ -549,7 +559,7 @@
             mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
         }
 
-        checkPendingColdStartValidity();
+        checkPendingColdStartValidityLocked();
         checkAndRemoveWaitingFor();
 
         traceEnd(cookie);
@@ -573,22 +583,24 @@
         enqueueUpdateRunningList();
     }
 
-    private void checkPendingColdStartValidity() {
+    @GuardedBy("mService")
+    private void checkPendingColdStartValidityLocked() {
         // There are a few cases where a starting process gets killed but AMS doesn't report
         // this event. So, once we start waiting for a pending cold start, periodically check
         // if the pending start is still valid and if not, clear it so that the queue doesn't
         // keep waiting for the process start forever.
-        synchronized (mService) {
-            // If there is no pending cold start, then nothing to do.
-            if (mRunningColdStart == null) {
-                return;
-            }
-            if (isPendingColdStartValid()) {
+        // If there is no pending cold start, then nothing to do.
+        if (mRunningColdStart == null) {
+            return;
+        }
+        if (isPendingColdStartValid()) {
+            if (!mCheckPendingColdStartQueued) {
                 mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY,
                         mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
-            } else {
-                clearInvalidPendingColdStart();
+                mCheckPendingColdStartQueued = true;
             }
+        } else {
+            clearInvalidPendingColdStart();
         }
     }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ddccce5..b00dcd6 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -144,6 +144,7 @@
 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;
@@ -1829,7 +1830,7 @@
                     // screen on or animating, promote UI
                     state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
                     state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
-                } else {
+                } else if (!app.getWindowProcessController().isShowingUiWhileDozing()) {
                     // screen off, restrict UI scheduling
                     state.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
                     state.setCurrentSchedulingGroup(SCHED_GROUP_RESTRICTED);
@@ -2418,9 +2419,23 @@
                     // 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()
+                            ? app.mProfile.getLastPss() : app.mProfile.getLastRss();
+
+                    // RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how
+                    // many shared pages a process uses. The threshold is increased if the flag for
+                    // reading RSS instead of PSS is enabled.
+                    //
+                    // 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 cachedRestoreThreshold =
+                            mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
+
                     if (!mService.mAppProfiler.isLastMemoryLevelNormal()
-                            && app.mProfile.getLastPss()
-                            >= mProcessList.getCachedRestoreThresholdKb()) {
+                            && lastPssOrRss >= cachedRestoreThreshold) {
                         state.setServiceHighRam(true);
                         state.setServiceB(true);
                         //Slog.i(TAG, "ADJ " + app + " high ram!");
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index b852ef5..d372108 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -67,10 +67,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.content.pm.ServiceInfo;
-import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -80,7 +78,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.function.Consumer;
@@ -504,6 +501,28 @@
         }
     }
 
+    /**
+     * A helper consumer for collecting processes that have not been reached yet. To avoid object
+     * allocations every OomAdjuster update, the results will be stored in
+     * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
+     * for setting it before usage, as well as, clearing the reachable state of each process in the
+     * list.
+     */
+    private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
+        public ArrayList<ProcessRecord> processList = null;
+        @Override
+        public void accept(ProcessRecord process) {
+            if (process.mState.isReachable()) {
+                return;
+            }
+            process.mState.setReachable(true);
+            processList.add(process);
+        }
+    }
+
+    private final UnreachedProcessCollector mUnreachedProcessCollector =
+            new UnreachedProcessCollector();
+
     OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
             ActiveUids activeUids) {
         this(service, processList, activeUids, createAdjusterThread());
@@ -755,23 +774,8 @@
         // We'll need to collect the upstream processes of the target apps here, because those
         // processes would potentially impact the procstate/adj via bindings.
         if (!fullUpdate) {
-            final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses,
-                    clientProcesses);
+            collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
 
-            // If any of its upstream processes are in a cycle,
-            // move them into the candidate targets.
-            if (containsCycle) {
-                // Add all client apps to the target process list.
-                for (int i = 0, size = clientProcesses.size(); i < size; i++) {
-                    final ProcessRecord client = clientProcesses.get(i);
-                    final UidRecord uidRec = client.getUidRecord();
-                    targetProcesses.add(client);
-                    if (uidRec != null) {
-                        uids.put(uidRec.getUid(), uidRec);
-                    }
-                }
-                clientProcesses.clear();
-            }
             for (int i = 0, size = targetProcesses.size(); i < size; i++) {
                 final ProcessRecord app = targetProcesses.valueAt(i);
                 app.mState.resetCachedInfo();
@@ -807,102 +811,36 @@
     }
 
     /**
-     * Collect the reversed reachable processes from the given {@code apps}, the result will be
-     * returned in the given {@code processes}, which will <em>NOT</em> include the processes from
-     * the given {@code apps}.
+     * Collect the client processes from the given {@code apps}, the result will be returned in the
+     * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
+     * {@code apps}.
      */
     @GuardedBy("mService")
-    private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+    private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
             ArrayList<ProcessRecord> clientProcesses) {
-        final ArrayDeque<ProcessRecord> queue = mTmpQueue;
-        queue.clear();
-        clientProcesses.clear();
-        for (int i = 0, size = apps.size(); i < size; i++) {
+        // Mark all of the provided apps as reachable to avoid including them in the client list.
+        final int appsSize = apps.size();
+        for (int i = 0; i < appsSize; i++) {
             final ProcessRecord app = apps.valueAt(i);
             app.mState.setReachable(true);
-            app.mState.setReversedReachable(true);
-            queue.offer(app);
         }
 
-        // Track if any of them reachables could include a cycle
-        boolean containsCycle = false;
-
-        // Scan upstreams of the process record
-        for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) {
-            if (!pr.mState.isReachable()) {
-                // If not in the given initial set of apps, add it.
-                clientProcesses.add(pr);
-            }
-            final ProcessServiceRecord psr = pr.mServices;
-            for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
-                final ServiceRecord s = psr.getRunningServiceAt(i);
-                final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
-                        s.getConnections();
-                for (int j = serviceConnections.size() - 1; j >= 0; j--) {
-                    final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
-                    for (int k = clist.size() - 1; k >= 0; k--) {
-                        final ConnectionRecord cr = clist.get(k);
-                        final ProcessRecord client = cr.binding.client;
-                        containsCycle |= client.mState.isReversedReachable();
-                        if (client.mState.isReversedReachable()) {
-                            continue;
-                        }
-                        queue.offer(client);
-                        client.mState.setReversedReachable(true);
-                    }
-                }
-            }
-            final ProcessProviderRecord ppr = pr.mProviders;
-            for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
-                final ContentProviderRecord cpr = ppr.getProviderAt(i);
-                for (int j = cpr.connections.size() - 1; j >= 0; j--) {
-                    final ContentProviderConnection conn = cpr.connections.get(j);
-                    final ProcessRecord client = conn.client;
-                    containsCycle |= client.mState.isReversedReachable();
-                    if (client.mState.isReversedReachable()) {
-                        continue;
-                    }
-                    queue.offer(client);
-                    client.mState.setReversedReachable(true);
-                }
-            }
-            // If this process is a sandbox itself, also add the app on whose behalf
-            // its running
-            if (pr.isSdkSandbox) {
-                for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
-                    ServiceRecord s = psr.getRunningServiceAt(is);
-                    ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
-                            s.getConnections();
-                    for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
-                        ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
-                        for (int i = clist.size() - 1; i >= 0; i--) {
-                            ConnectionRecord cr = clist.get(i);
-                            ProcessRecord attributedApp = cr.binding.attributedClient;
-                            if (attributedApp == null || attributedApp == pr) {
-                                continue;
-                            }
-                            containsCycle |= attributedApp.mState.isReversedReachable();
-                            if (attributedApp.mState.isReversedReachable()) {
-                                continue;
-                            }
-                            queue.offer(attributedApp);
-                            attributedApp.mState.setReversedReachable(true);
-                        }
-                    }
-                }
-            }
+        clientProcesses.clear();
+        mUnreachedProcessCollector.processList = clientProcesses;
+        for (int i = 0; i < appsSize; i++) {
+            final ProcessRecord app = apps.valueAt(i);
+            app.forEachClient(mUnreachedProcessCollector);
         }
+        mUnreachedProcessCollector.processList = null;
 
         // Reset the temporary bits.
         for (int i = clientProcesses.size() - 1; i >= 0; i--) {
-            clientProcesses.get(i).mState.setReversedReachable(false);
+            clientProcesses.get(i).mState.setReachable(false);
         }
         for (int i = 0, size = apps.size(); i < size; i++) {
             final ProcessRecord app = apps.valueAt(i);
             app.mState.setReachable(false);
-            app.mState.setReversedReachable(false);
         }
-        return containsCycle;
     }
 
     @GuardedBy({"mService", "mProcLock"})
@@ -917,10 +855,6 @@
         final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
         final int adjTarget = mProcessRecordAdjNodes.size() - 1;
 
-        final int appUid = !fullUpdate && targetProcesses.size() > 0
-                ? targetProcesses.valueAt(0).uid : -1;
-        final int logUid = mService.mCurOomAdjUid;
-
         mAdjSeq++;
         // All apps to be updated will be moved to the lowest slot.
         if (fullUpdate) {
@@ -974,7 +908,7 @@
             // We don't update the adj list since we're resetting it below.
         }
 
-        // Now nodes are set into their slots, without facting in the bindings.
+        // Now nodes are set into their slots, without factoring in the bindings.
         // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
         //
         // The whole rationale here is that, the bindings from client to host app, won't elevate
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2efac12..4ff34b1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -34,7 +34,7 @@
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.startWebView;
-import static android.system.OsConstants.*;
+import static android.system.OsConstants.EAGAIN;
 
 import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
@@ -94,6 +94,7 @@
 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;
@@ -132,7 +133,6 @@
 import com.android.internal.app.ProcessMap;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
@@ -3298,8 +3298,6 @@
             // about the process state of the isolated UID *before* it is registered with the
             // owning application.
             mService.mBatteryStatsService.addIsolatedUid(uid, info.uid);
-            FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid,
-                    FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
         }
         final ProcessRecord r = new ProcessRecord(mService, info, proc, uid,
                 sdkSandboxClientAppPackage,
@@ -4594,6 +4592,8 @@
                         r.mProfile.getLastPss() * 1024, new StringBuilder()));
                 proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString(
                         r.mProfile.getLastSwapPss() * 1024, new StringBuilder()));
+                // TODO(b/296454553): This proto field should be replaced with last cached RSS once
+                // AppProfiler is no longer collecting PSS.
                 proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
                         r.mProfile.getLastCachedPss() * 1024, new StringBuilder()));
                 proto.write(ProcessOomProto.Detail.CACHED, state.isCached());
@@ -4725,12 +4725,20 @@
                 pw.print("    ");
                 pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
                 pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
-                pw.print(" lastPss=");
-                DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
-                pw.print(" lastSwapPss=");
-                DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024);
-                pw.print(" lastCachedPss=");
-                DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024);
+                // These values won't be collected if the flag is enabled.
+                if (!Flags.removeAppProfilerPssCollection()) {
+                    pw.print(" lastPss=");
+                    DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
+                    pw.print(" lastSwapPss=");
+                    DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024);
+                    pw.print(" lastCachedPss=");
+                    DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024);
+                } else {
+                    pw.print(" lastRss=");
+                    DebugUtils.printSizeValue(pw, r.mProfile.getLastRss() * 1024);
+                    pw.print(" lastCachedRss=");
+                    DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedRss() * 1024);
+                }
                 pw.println();
                 pw.print(prefix);
                 pw.print("    ");
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 354f3d3..8ca64f8 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,6 +23,7 @@
 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;
@@ -32,7 +33,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.ProcessList.ProcStateMemTracker;
 import com.android.server.power.stats.BatteryStatsImpl;
 
@@ -42,6 +42,8 @@
 
 /**
  * Profiling info of the process, such as PSS, cpu, etc.
+ *
+ * TODO(b/297542292): Update PSS names with RSS once AppProfiler's PSS profiling has been replaced.
  */
 final class ProcessProfileRecord {
     final ProcessRecord mApp;
@@ -76,7 +78,7 @@
      * Initial memory pss of process for idle maintenance.
      */
     @GuardedBy("mProfilerLock")
-    private long mInitialIdlePss;
+    private long mInitialIdlePssOrRss;
 
     /**
      * Last computed memory pss.
@@ -109,6 +111,14 @@
     private long mLastRss;
 
     /**
+     * Last computed rss when in cached state.
+     *
+     * This value is not set or retrieved unless Flags.removeAppProfilerPssCollection() is true.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastCachedRss;
+
+    /**
      * Cache of last retrieve memory info, to throttle how frequently apps can request it.
      */
     @GuardedBy("mProfilerLock")
@@ -347,13 +357,13 @@
     }
 
     @GuardedBy("mProfilerLock")
-    long getInitialIdlePss() {
-        return mInitialIdlePss;
+    long getInitialIdlePssOrRss() {
+        return mInitialIdlePssOrRss;
     }
 
     @GuardedBy("mProfilerLock")
-    void setInitialIdlePss(long initialIdlePss) {
-        mInitialIdlePss = initialIdlePss;
+    void setInitialIdlePssOrRss(long initialIdlePssOrRss) {
+        mInitialIdlePssOrRss = initialIdlePssOrRss;
     }
 
     @GuardedBy("mProfilerLock")
@@ -377,6 +387,16 @@
     }
 
     @GuardedBy("mProfilerLock")
+    long getLastCachedRss() {
+        return mLastCachedRss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastCachedRss(long lastCachedRss) {
+        mLastCachedRss = lastCachedRss;
+    }
+
+    @GuardedBy("mProfilerLock")
     long getLastSwapPss() {
         return mLastSwapPss;
     }
@@ -530,26 +550,6 @@
         }
     }
 
-    void reportCachedKill() {
-        synchronized (mService.mProcessStats.mLock) {
-            final ProcessState tracker = mBaseProcessTracker;
-            if (tracker != null) {
-                final PackageList pkgList = mApp.getPkgList();
-                synchronized (pkgList) {
-                    tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss);
-                    pkgList.forEachPackageProcessStats(holder ->
-                            FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
-                                getUidForAttribution(mApp),
-                                holder.state.getName(),
-                                holder.state.getPackage(),
-                                mLastCachedPss,
-                                holder.appVersion)
-                    );
-                }
-            }
-        }
-    }
-
     void setProcessTrackerState(int procState, int memFactor) {
         synchronized (mService.mProcessStats.mLock) {
             final ProcessState tracker = mBaseProcessTracker;
@@ -676,27 +676,46 @@
     @GuardedBy("mService")
     void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
         synchronized (mProfilerLock) {
-            pw.print(prefix);
-            pw.print("lastPssTime=");
-            TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
-            pw.print(" pssProcState=");
-            pw.print(mPssProcState);
-            pw.print(" pssStatType=");
-            pw.print(mPssStatType);
-            pw.print(" nextPssTime=");
-            TimeUtils.formatDuration(mNextPssTime, nowUptime, pw);
-            pw.println();
-            pw.print(prefix);
-            pw.print("lastPss=");
-            DebugUtils.printSizeValue(pw, mLastPss * 1024);
-            pw.print(" lastSwapPss=");
-            DebugUtils.printSizeValue(pw, mLastSwapPss * 1024);
-            pw.print(" lastCachedPss=");
-            DebugUtils.printSizeValue(pw, mLastCachedPss * 1024);
-            pw.print(" lastCachedSwapPss=");
-            DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
-            pw.print(" lastRss=");
-            DebugUtils.printSizeValue(pw, mLastRss * 1024);
+            // TODO(b/297542292): Remove this case once PSS profiling is replaced
+            if (!Flags.removeAppProfilerPssCollection()) {
+                pw.print(prefix);
+                pw.print("lastPssTime=");
+                TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
+                pw.print(" pssProcState=");
+                pw.print(mPssProcState);
+                pw.print(" pssStatType=");
+                pw.print(mPssStatType);
+                pw.print(" nextPssTime=");
+                TimeUtils.formatDuration(mNextPssTime, nowUptime, pw);
+                pw.println();
+                pw.print(prefix);
+                pw.print("lastPss=");
+                DebugUtils.printSizeValue(pw, mLastPss * 1024);
+                pw.print(" lastSwapPss=");
+                DebugUtils.printSizeValue(pw, mLastSwapPss * 1024);
+                pw.print(" lastCachedPss=");
+                DebugUtils.printSizeValue(pw, mLastCachedPss * 1024);
+                pw.print(" lastCachedSwapPss=");
+                DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
+                pw.print(" lastRss=");
+                DebugUtils.printSizeValue(pw, mLastRss * 1024);
+            } else {
+                pw.print(prefix);
+                pw.print("lastRssTime=");
+                TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
+                pw.print(" rssProcState=");
+                pw.print(mPssProcState);
+                pw.print(" rssStatType=");
+                pw.print(mPssStatType);
+                pw.print(" nextRssTime=");
+                TimeUtils.formatDuration(mNextPssTime, nowUptime, pw);
+                pw.println();
+                pw.print(prefix);
+                pw.print("lastRss=");
+                DebugUtils.printSizeValue(pw, mLastRss * 1024);
+                pw.print(" lastCachedRss=");
+                DebugUtils.printSizeValue(pw, mLastCachedRss * 1024);
+            }
             pw.println();
             pw.print(prefix);
             pw.print("trimMemoryLevel=");
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2c6e598..b2082d9 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,8 +69,10 @@
 import com.android.server.wm.WindowProcessListener;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Full information about a particular process that
@@ -1613,4 +1615,50 @@
     public boolean wasForceStopped() {
         return mWasForceStopped;
     }
+
+    /**
+     * Traverses all client processes and feed them to consumer.
+     */
+    @GuardedBy("mProcLock")
+    void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
+        for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
+            final ServiceRecord s = mServices.getRunningServiceAt(i);
+            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                    s.getConnections();
+            for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+                final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+                for (int k = clist.size() - 1; k >= 0; k--) {
+                    final ConnectionRecord cr = clist.get(k);
+                    consumer.accept(cr.binding.client);
+                }
+            }
+        }
+        for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
+            final ContentProviderRecord cpr = mProviders.getProviderAt(i);
+            for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+                final ContentProviderConnection conn = cpr.connections.get(j);
+                consumer.accept(conn.client);
+            }
+        }
+        // If this process is a sandbox itself, also add the app on whose behalf
+        // its running
+        if (isSdkSandbox) {
+            for (int is = mServices.numberOfRunningServices() - 1; is >= 0; is--) {
+                ServiceRecord s = mServices.getRunningServiceAt(is);
+                ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                        s.getConnections();
+                for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+                    ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+                    for (int i = clist.size() - 1; i >= 0; i--) {
+                        ConnectionRecord cr = clist.get(i);
+                        ProcessRecord attributedApp = cr.binding.attributedClient;
+                        if (attributedApp == null || attributedApp == this) {
+                            continue;
+                        }
+                        consumer.accept(attributedApp);
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 27c0876..5ad921f 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -29,6 +29,7 @@
 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;
@@ -378,12 +379,6 @@
     private boolean mReachable;
 
     /**
-     * Whether or not this process is reversed reachable from given process.
-     */
-    @GuardedBy("mService")
-    private boolean mReversedReachable;
-
-    /**
      * The most recent time when the last visible activity within this process became invisible.
      *
      * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
@@ -996,16 +991,6 @@
     }
 
     @GuardedBy("mService")
-    boolean isReversedReachable() {
-        return mReversedReachable;
-    }
-
-    @GuardedBy("mService")
-    void setReversedReachable(boolean reversedReachable) {
-        mReversedReachable = reversedReachable;
-    }
-
-    @GuardedBy("mService")
     void resetCachedInfo() {
         mCachedHasActivities = VALUE_INVALID;
         mCachedIsHeavyWeight = VALUE_INVALID;
@@ -1366,7 +1351,12 @@
         }
         if (mNotCachedSinceIdle) {
             pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
-            pw.print(" initialIdlePss="); pw.println(mApp.mProfile.getInitialIdlePss());
+            if (!Flags.removeAppProfilerPssCollection()) {
+                pw.print(" initialIdlePss=");
+            } else {
+                pw.print(" initialIdleRss=");
+            }
+            pw.println(mApp.mProfile.getInitialIdlePssOrRss());
         }
         if (hasTopUi() || hasOverlayUi() || mRunningRemoteAnimation) {
             pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 028be88..69e3aaf 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -123,6 +123,7 @@
         "angle",
         "app_widgets",
         "arc_next",
+        "avic",
         "bluetooth",
         "build",
         "biometrics",
@@ -140,6 +141,7 @@
         "context_hub",
         "core_experiments_team_internal",
         "core_graphics",
+        "dck_framework",
         "game",
         "haptics",
         "hardware_backed_security_mainline",
@@ -150,11 +152,13 @@
         "mainline_sdk",
         "media_audio",
         "media_drm",
+        "media_reliability",
         "media_tv",
         "media_solutions",
         "nfc",
         "pdf_viewer",
         "pixel_audio_android",
+        "pixel_bluetooth",
         "pixel_system_sw_touch",
         "pixel_watch",
         "platform_security",
@@ -184,6 +188,7 @@
         "wear_security",
         "wear_system_health",
         "wear_systems",
+        "wear_sysui",
         "window_surfaces",
         "windowing_frontend",
     };
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 108f53f..0916967 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,6 +20,7 @@
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
 import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT;
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -150,7 +151,7 @@
     }
 
     @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
+    public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) {
         synchronized (mLock) {
             SparseIntArray opModes = mUidModes.get(uid, null);
             if (opModes == null) {
@@ -176,7 +177,7 @@
     }
 
     @Override
-    public int getUidMode(int uid, int op) {
+    public int getUidMode(int uid, String persistentDeviceId, int op) {
         synchronized (mLock) {
             SparseIntArray opModes = mUidModes.get(uid, null);
             if (opModes == null) {
@@ -187,7 +188,7 @@
     }
 
     @Override
-    public boolean setUidMode(int uid, int op, int mode) {
+    public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) {
         final int defaultMode = AppOpsManager.opToDefaultMode(op);
         List<AppOpsModeChangedListener> listenersCopy;
         synchronized (mLock) {
@@ -305,26 +306,6 @@
     }
 
     @Override
-    public boolean areUidModesDefault(int uid) {
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid);
-            return (opModes == null || opModes.size() <= 0);
-        }
-    }
-
-    @Override
-    public boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
-                return true;
-            }
-            SparseIntArray opModes = packageModes.get(packageName);
-            return (opModes == null || opModes.size() <= 0);
-        }
-    }
-
-    @Override
     public boolean removePackage(String packageName, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
@@ -349,7 +330,7 @@
     }
 
     @Override
-    public SparseBooleanArray getForegroundOps(int uid) {
+    public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) {
         SparseBooleanArray result = new SparseBooleanArray();
         synchronized (mLock) {
             SparseIntArray modes = mUidModes.get(uid);
@@ -626,9 +607,17 @@
         for (final String pkg : packagesDeclaringPermission) {
             for (int userId : userIds) {
                 final int uid = pmi.getPackageUid(pkg, 0, userId);
-                final int oldMode = getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+                final int oldMode =
+                        getUidMode(
+                                uid,
+                                PERSISTENT_DEVICE_ID_DEFAULT,
+                                OP_SCHEDULE_EXACT_ALARM);
                 if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
-                    setUidMode(uid, OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
+                    setUidMode(
+                            uid,
+                            PERSISTENT_DEVICE_ID_DEFAULT,
+                            OP_SCHEDULE_EXACT_ALARM,
+                            MODE_ALLOWED);
                 }
             }
             // This appop is meant to be controlled at a uid level. So we leave package modes as
@@ -661,7 +650,10 @@
                 final int flags = permissionManager.getPermissionFlags(pkg, permissionName,
                         UserHandle.of(userId));
                 if ((flags & PackageManager.FLAG_PERMISSION_USER_SET) == 0) {
-                    setUidMode(uid, OP_USE_FULL_SCREEN_INTENT,
+                    setUidMode(
+                            uid,
+                            PERSISTENT_DEVICE_ID_DEFAULT,
+                            OP_USE_FULL_SCREEN_INTENT,
                             AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT));
                 }
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index 60d17cd..f056f6b 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -59,8 +59,9 @@
      * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
      * Returns an empty SparseIntArray if nothing is set.
      * @param uid for which we need the app-ops and their modes.
+     * @param persistentDeviceId device for which we need the app-ops and their modes
      */
-    SparseIntArray getNonDefaultUidModes(int uid);
+    SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId);
 
     /**
      * Returns a copy of non-default app-ops with op as keys and their modes as values for a package
@@ -75,20 +76,22 @@
      * Returns the app-op mode for a particular app-op of a uid.
      * Returns default op mode if the op mode for particular uid and op is not set.
      * @param uid user id for which we need the mode.
+     * @param persistentDeviceId device for which we need the mode
      * @param op app-op for which we need the mode.
      * @return mode of the app-op.
      */
-    int getUidMode(int uid, int op);
+    int getUidMode(int uid, String persistentDeviceId, int op);
 
     /**
      * Set the app-op mode for a particular uid and op.
      * The mode is not set if the mode is the same as the default mode for the op.
      * @param uid user id for which we want to set the mode.
+     * @param persistentDeviceId device for which we want to set the mode.
      * @param op app-op for which we want to set the mode.
      * @param mode mode for the app-op.
      * @return true if op mode is changed.
      */
-    boolean setUidMode(int uid, int op, @Mode int mode);
+    boolean setUidMode(int uid, String persistentDeviceId, int op, @Mode int mode);
 
     /**
      * Gets the app-op mode for a particular package.
@@ -124,31 +127,17 @@
     void removeUid(int uid);
 
     /**
-     * Returns true if all uid modes for this uid are
-     * in default state.
-     * @param uid user id
-     */
-    boolean areUidModesDefault(int uid);
-
-    /**
-     * Returns true if all package modes for this package name are
-     * in default state.
-     * @param packageName package name.
-     * @param userId user id associated with the package.
-     */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
-
-    /**
      * Stop tracking app-op modes for all uid and packages.
      */
     void clearAllModes();
 
     /**
      * @param uid UID to query foreground ops for.
+     * @param persistentDeviceId device to query foreground ops for
      * @return SparseBooleanArray where the keys are the op codes for which their modes are
      * MODE_FOREGROUND for the passed UID.
      */
-    SparseBooleanArray getForegroundOps(int uid);
+    SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId);
 
     /**
      *
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
index 3fee59b..f6da166 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
@@ -60,9 +60,9 @@
     }
 
     @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
+    public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) {
         Log.i(LOG_TAG, "getNonDefaultUidModes(uid = " + uid + ")");
-        return mService.getNonDefaultUidModes(uid);
+        return mService.getNonDefaultUidModes(uid, persistentDeviceId);
     }
 
     @Override
@@ -73,15 +73,15 @@
     }
 
     @Override
-    public int getUidMode(int uid, int op) {
+    public int getUidMode(int uid, String persistentDeviceId, int op) {
         Log.i(LOG_TAG, "getUidMode(uid = " + uid + ", op = " + op + ")");
-        return mService.getUidMode(uid, op);
+        return mService.getUidMode(uid, persistentDeviceId, op);
     }
 
     @Override
-    public boolean setUidMode(int uid, int op, int mode) {
+    public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) {
         Log.i(LOG_TAG, "setUidMode(uid = " + uid + ", op = " + op + ", mode = " + mode + ")");
-        return mService.setUidMode(uid, op, mode);
+        return mService.setUidMode(uid, persistentDeviceId, op, mode);
     }
 
     @Override
@@ -111,28 +111,15 @@
     }
 
     @Override
-    public boolean areUidModesDefault(int uid) {
-        Log.i(LOG_TAG, "areUidModesDefault(uid = " + uid + ")");
-        return mService.areUidModesDefault(uid);
-    }
-
-    @Override
-    public boolean arePackageModesDefault(String packageName, int userId) {
-        Log.i(LOG_TAG, "arePackageModesDefault(packageName = " + packageName + ", userId = "
-                + userId + ")");
-        return mService.arePackageModesDefault(packageName, userId);
-    }
-
-    @Override
     public void clearAllModes() {
         Log.i(LOG_TAG, "clearAllModes()");
         mService.clearAllModes();
     }
 
     @Override
-    public SparseBooleanArray getForegroundOps(int uid) {
+    public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) {
         Log.i(LOG_TAG, "getForegroundOps(uid = " + uid + ")");
-        return mService.getForegroundOps(uid);
+        return mService.getForegroundOps(uid, persistentDeviceId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
index c0cc8b1..55cf7ed 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
@@ -81,11 +81,11 @@
     }
 
     @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
+    public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
         try {
-            return mService.getNonDefaultUidModes(uid);
+            return mService.getNonDefaultUidModes(uid, persistentDeviceId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -103,20 +103,21 @@
     }
 
     @Override
-    public int getUidMode(int uid, int op) {
+    public int getUidMode(int uid, String persistentDeviceId, int op) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode");
         try {
-            return mService.getUidMode(uid, op);
+            return mService.getUidMode(uid, persistentDeviceId, op);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) {
+    public boolean setUidMode(
+            int uid, String persistentDeviceId, int op, @AppOpsManager.Mode int mode) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode");
         try {
-            return mService.setUidMode(uid, op, mode);
+            return mService.setUidMode(uid, persistentDeviceId, op, mode);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -168,28 +169,6 @@
     }
 
     @Override
-    public boolean areUidModesDefault(int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#areUidModesDefault");
-        try {
-            return mService.areUidModesDefault(uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean arePackageModesDefault(String packageName, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#arePackageModesDefault");
-        try {
-            return mService.arePackageModesDefault(packageName, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
     public void clearAllModes() {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingAppOpsCheckingServiceInterfaceImpl#clearAllModes");
@@ -201,11 +180,11 @@
     }
 
     @Override
-    public SparseBooleanArray getForegroundOps(int uid) {
+    public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps");
         try {
-            return mService.getForegroundOps(uid);
+            return mService.getForegroundOps(uid, persistentDeviceId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 14aab13..3446737 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -63,6 +63,7 @@
 import static android.app.AppOpsManager.opRestrictsRead;
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
@@ -1349,7 +1350,10 @@
 
                 SparseBooleanArray foregroundOps = new SparseBooleanArray();
 
-                SparseBooleanArray uidForegroundOps = mAppOpsCheckingService.getForegroundOps(uid);
+                // TODO(b/299330771): Check uidForegroundOps for all devices.
+                SparseBooleanArray uidForegroundOps =
+                        mAppOpsCheckingService.getForegroundOps(
+                                uid, PERSISTENT_DEVICE_ID_DEFAULT);
                 for (int i = 0; i < uidForegroundOps.size(); i++) {
                     foregroundOps.put(uidForegroundOps.keyAt(i), true);
                 }
@@ -1369,10 +1373,16 @@
                         continue;
                     }
                     final int code = foregroundOps.keyAt(fgi);
-
-                    if (mAppOpsCheckingService.getUidMode(uidState.uid, code)
+                    // TODO(b/299330771): Notify op changes for all relevant devices.
+                    if (mAppOpsCheckingService.getUidMode(
+                                            uidState.uid,
+                                            PERSISTENT_DEVICE_ID_DEFAULT,
+                                            code)
                                     != AppOpsManager.opToDefaultMode(code)
-                            && mAppOpsCheckingService.getUidMode(uidState.uid, code)
+                            && mAppOpsCheckingService.getUidMode(
+                                            uidState.uid,
+                                            PERSISTENT_DEVICE_ID_DEFAULT,
+                                            code)
                                     == AppOpsManager.MODE_FOREGROUND) {
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForAllPkgsInUid,
@@ -1489,7 +1499,11 @@
     @Nullable
     private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
             @Nullable int[] ops) {
-        final SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid);
+        // TODO(b/299330771): Make this methods device-aware, currently it represents only the
+        // primary device.
+        final SparseIntArray opModes =
+                mAppOpsCheckingService.getNonDefaultUidModes(
+                        uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
         if (opModes == null) {
             return null;
         }
@@ -1844,16 +1858,22 @@
                 uidState = new UidState(uid);
                 mUidStates.put(uid, uidState);
             }
-            if (mAppOpsCheckingService.getUidMode(uidState.uid, code)
+            // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime
+            //  permissions is deprecated.
+            if (mAppOpsCheckingService.getUidMode(
+                            uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code)
                     != AppOpsManager.opToDefaultMode(code)) {
-                previousMode = mAppOpsCheckingService.getUidMode(uidState.uid, code);
+                previousMode =
+                        mAppOpsCheckingService.getUidMode(
+                                uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code);
             } else {
                 // doesn't look right but is legacy behavior.
                 previousMode = MODE_DEFAULT;
             }
 
             mIgnoredCallback = permissionPolicyCallback;
-            if (!mAppOpsCheckingService.setUidMode(uidState.uid, code, mode)) {
+            if (!mAppOpsCheckingService.setUidMode(
+                    uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) {
                 return;
             }
             if (mode != MODE_ERRORED && mode != previousMode) {
@@ -2275,8 +2295,10 @@
             boolean changed = false;
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
                 UidState uidState = mUidStates.valueAt(i);
-
-                SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid);
+                // TODO(b/299330771): Check non default modes for all devices.
+                SparseIntArray opModes =
+                        mAppOpsCheckingService.getNonDefaultUidModes(
+                                uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
                 if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
                     final int uidOpCount = opModes.size();
                     for (int j = uidOpCount - 1; j >= 0; j--) {
@@ -2285,7 +2307,12 @@
                             int previousMode = opModes.valueAt(j);
                             int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
                                     AppOpsManager.opToDefaultMode(code);
-                            mAppOpsCheckingService.setUidMode(uidState.uid, code, newMode);
+                            // TODO(b/299330771): Set mode for all necessary devices.
+                            mAppOpsCheckingService.setUidMode(
+                                    uidState.uid,
+                                    PERSISTENT_DEVICE_ID_DEFAULT,
+                                    code,
+                                    newMode);
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         previousMode, mOpModeWatchers.get(code));
@@ -2601,10 +2628,14 @@
             }
             code = AppOpsManager.opToSwitch(code);
             UidState uidState = getUidStateLocked(uid, false);
+            // TODO(b/299330771): Check mode for the relevant device.
             if (uidState != null
-                    && mAppOpsCheckingService.getUidMode(uidState.uid, code)
+                    && mAppOpsCheckingService.getUidMode(
+                                    uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code)
                             != AppOpsManager.opToDefaultMode(code)) {
-                final int rawMode = mAppOpsCheckingService.getUidMode(uidState.uid, code);
+                final int rawMode =
+                        mAppOpsCheckingService.getUidMode(
+                                uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code);
                 return raw ? rawMode : uidState.evalMode(code, rawMode);
             }
             Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
@@ -2851,13 +2882,19 @@
                 return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
                         packageName);
             }
+            // TODO(b/299330771): Check mode for the relevant device.
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)
+            if (mAppOpsCheckingService.getUidMode(
+                            uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
-                                code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode));
+                                code,
+                                mAppOpsCheckingService.getUidMode(
+                                        uidState.uid,
+                                        PERSISTENT_DEVICE_ID_DEFAULT,
+                                        switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
@@ -3396,13 +3433,19 @@
             isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
                     false);
             final int switchCode = AppOpsManager.opToSwitch(code);
+            // TODO(b/299330771): Check mode for the relevant device.
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)
+            if (mAppOpsCheckingService.getUidMode(
+                            uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
-                                code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode));
+                                code,
+                                mAppOpsCheckingService.getUidMode(
+                                        uidState.uid,
+                                        PERSISTENT_DEVICE_ID_DEFAULT,
+                                        switchCode));
                 if (!shouldStartForMode(uidMode, startIfModeDefault)) {
                     if (DEBUG) {
                         Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
@@ -3511,13 +3554,19 @@
             isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
                     false);
             final int switchCode = AppOpsManager.opToSwitch(code);
+            // TODO(b/299330771): Check mode for the relevant device.
             // If there is a non-default mode per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)
+            if (mAppOpsCheckingService.getUidMode(
+                            uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
-                                code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode));
+                                code,
+                                mAppOpsCheckingService.getUidMode(
+                                        uidState.uid,
+                                        PERSISTENT_DEVICE_ID_DEFAULT,
+                                        switchCode));
                 if (!shouldStartForMode(uidMode, startIfModeDefault)) {
                     if (DEBUG) {
                         Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
@@ -5664,8 +5713,10 @@
             }
             for (int i=0; i<mUidStates.size(); i++) {
                 UidState uidState = mUidStates.valueAt(i);
+                // TODO(b/299330771): Get modes for all devices.
                 final SparseIntArray opModes =
-                        mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid);
+                        mAppOpsCheckingService.getNonDefaultUidModes(
+                                uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
                 final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
 
                 if (dumpWatchers || dumpHistory) {
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java
index de73a55..c9fa9e6 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java
@@ -65,9 +65,9 @@
     }
 
     @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
-        SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid);
-        SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid);
+    public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) {
+        SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid, persistentDeviceId);
+        SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid, persistentDeviceId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("getNonDefaultUidModes");
@@ -89,9 +89,9 @@
     }
 
     @Override
-    public int getUidMode(int uid, int op) {
-        int oldVal = mOldImplementation.getUidMode(uid, op);
-        int newVal = mNewImplementation.getUidMode(uid, op);
+    public int getUidMode(int uid, String persistentDeviceId, int op) {
+        int oldVal = mOldImplementation.getUidMode(uid, persistentDeviceId, op);
+        int newVal = mNewImplementation.getUidMode(uid, persistentDeviceId, op);
 
         if (oldVal != newVal) {
             signalImplDifference("getUidMode");
@@ -101,9 +101,9 @@
     }
 
     @Override
-    public boolean setUidMode(int uid, int op, int mode) {
-        boolean oldVal = mOldImplementation.setUidMode(uid, op, mode);
-        boolean newVal = mNewImplementation.setUidMode(uid, op, mode);
+    public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) {
+        boolean oldVal = mOldImplementation.setUidMode(uid, persistentDeviceId, op, mode);
+        boolean newVal = mNewImplementation.setUidMode(uid, persistentDeviceId, op, mode);
 
         if (oldVal != newVal) {
             signalImplDifference("setUidMode");
@@ -149,39 +149,15 @@
     }
 
     @Override
-    public boolean areUidModesDefault(int uid) {
-        boolean oldVal = mOldImplementation.areUidModesDefault(uid);
-        boolean newVal = mNewImplementation.areUidModesDefault(uid);
-
-        if (oldVal != newVal) {
-            signalImplDifference("areUidModesDefault");
-        }
-
-        return newVal;
-    }
-
-    @Override
-    public boolean arePackageModesDefault(String packageName, int userId) {
-        boolean oldVal = mOldImplementation.arePackageModesDefault(packageName, userId);
-        boolean newVal = mNewImplementation.arePackageModesDefault(packageName, userId);
-
-        if (oldVal != newVal) {
-            signalImplDifference("arePackageModesDefault");
-        }
-
-        return newVal;
-    }
-
-    @Override
     public void clearAllModes() {
         mOldImplementation.clearAllModes();
         mNewImplementation.clearAllModes();
     }
 
     @Override
-    public SparseBooleanArray getForegroundOps(int uid) {
-        SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid);
-        SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid);
+    public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) {
+        SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid, persistentDeviceId);
+        SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid, persistentDeviceId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("getForegroundOps");
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1e38c0f..3f27ca0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -107,6 +109,7 @@
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
+import android.media.AudioTrack;
 import android.media.BluetoothProfileConnectionInfo;
 import android.media.IAudioDeviceVolumeDispatcher;
 import android.media.IAudioFocusDispatcher;
@@ -118,6 +121,7 @@
 import android.media.ICommunicationDeviceDispatcher;
 import android.media.IDeviceVolumeBehaviorDispatcher;
 import android.media.IDevicesForAttributesCallback;
+import android.media.ILoudnessCodecUpdatesDispatcher;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IPreferredMixerAttributesDispatcher;
@@ -132,6 +136,9 @@
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IStreamAliasingDispatcher;
 import android.media.IVolumeController;
+import android.media.LoudnessCodecConfigurator;
+import android.media.LoudnessCodecInfo;
+import android.media.MediaCodec;
 import android.media.MediaMetrics;
 import android.media.MediaRecorder.AudioSource;
 import android.media.PlayerBase;
@@ -349,7 +356,7 @@
     }
 
     /*package*/ boolean isPlatformAutomotive() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        return mPlatformType == AudioSystem.PLATFORM_AUTOMOTIVE;
     }
 
     /** The controller for the volume UI. */
@@ -943,6 +950,8 @@
 
     private final SoundDoseHelper mSoundDoseHelper;
 
+    private final LoudnessCodecHelper mLoudnessCodecHelper;
+
     private final HardeningEnforcer mHardeningEnforcer;
 
     private final Object mSupportedSystemUsagesLock = new Object();
@@ -1279,6 +1288,8 @@
         readPersistedSettings();
         readUserRestrictions();
 
+        mLoudnessCodecHelper = new LoudnessCodecHelper(this);
+
         mPlaybackMonitor =
                 new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
                         device -> onMuteAwaitConnectionTimeout(device));
@@ -3997,7 +4008,8 @@
             }
             setStreamVolume(groupedStream, index, flags, /*device*/ null,
                     callingPackage, callingPackage,
-                    attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/);
+                    attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/,
+                    true /*canChangeMuteAndUpdateController*/);
         }
     }
 
@@ -4116,7 +4128,9 @@
             setStreamVolumeWithAttributionInt(vi.getStreamType(),
                     mStreamStates[vi.getStreamType()].getMinIndex(),
                     /*flags*/ 0,
-                    ada, callingPackage, null);
+                    ada, callingPackage, null,
+                    //TODO handle unmuting of current audio device
+                    false /*canChangeMuteAndUpdateController*/);
             return;
         }
 
@@ -4142,7 +4156,8 @@
             }
         }
         setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
-                ada, callingPackage, null);
+                ada, callingPackage, null,
+                false /*canChangeMuteAndUpdateController*/);
     }
 
     /** Retain API for unsupported app usage */
@@ -4225,7 +4240,7 @@
             return;
         }
         setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
-                callingPackage, attributionTag);
+                callingPackage, attributionTag, true /*canChangeMuteAndUpdateController*/);
     }
 
     /**
@@ -4238,10 +4253,18 @@
      *               for which volume is being changed
      * @param callingPackage client side-provided package name of caller, not to be trusted
      * @param attributionTag client side-provided attribution name, not to be trusted
+     * @param canChangeMuteAndUpdateController true if the calling method is a path where
+     *          the volume change is allowed to update the mute state as well as update
+     *          the volume controller (the UI). This is intended to be true for a call coming
+     *          from AudioManager.setStreamVolume (which is here
+     *          {@link #setStreamVolumeForUid(int, int, int, String, int, int, UserHandle, int)},
+     *          and false when coming from AudioDeviceVolumeManager.setDeviceVolume (which is here
+     *          {@link #setDeviceVolume(VolumeInfo, AudioDeviceAttributes, String)}
      */
     protected void setStreamVolumeWithAttributionInt(int streamType, int index, int flags,
-            @Nullable AudioDeviceAttributes device,
-            String callingPackage, String attributionTag) {
+            @Nullable AudioDeviceAttributes ada,
+            String callingPackage, String attributionTag,
+            boolean canChangeMuteAndUpdateController) {
         if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
             Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
                     + " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);
@@ -4264,14 +4287,18 @@
             return;
         }
 
-        if (device == null) {
+        if (ada == null) {
             // call was already logged in setDeviceVolume()
+            final int deviceType = getDeviceForStream(streamType);
             sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
-                    index/*val1*/, flags/*val2*/, callingPackage));
+                    index/*val1*/, flags/*val2*/, getStreamVolume(streamType, deviceType) /*val3*/,
+                    callingPackage));
+            ada = new AudioDeviceAttributes(deviceType /*nativeType*/, "" /*address*/);
         }
-        setStreamVolume(streamType, index, flags, device,
+        setStreamVolume(streamType, index, flags, ada,
                 callingPackage, callingPackage, attributionTag,
-                Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
+                Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission(),
+                canChangeMuteAndUpdateController);
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND)
@@ -4370,6 +4397,9 @@
         if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
             mSoundDoseHelper.scheduleMusicActiveCheck();
         }
+
+        mLoudnessCodecHelper.updateCodecParameters(configs);
+
         // Update playback active state for all apps in audio mode stack.
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
@@ -4452,6 +4482,18 @@
                 null /* playbackConfigs */, configs /* recordConfigs */);
     }
 
+    private void dumpFlags(PrintWriter pw) {
+        pw.println("\nFun with Flags: ");
+        pw.println("\tandroid.media.audio.Flags.autoPublicVolumeApiHardening:"
+                + autoPublicVolumeApiHardening());
+        pw.println("\tandroid.media.audio.Flags.focusFreezeTestApi:"
+                + focusFreezeTestApi());
+        pw.println("\tcom.android.media.audio.Flags.bluetoothMacAddressAnonymization:"
+                + bluetoothMacAddressAnonymization());
+        pw.println("\tcom.android.media.audio.Flags.disablePrescaleAbsoluteVolume:"
+                + disablePrescaleAbsoluteVolume());
+    }
+
     private void dumpAudioMode(PrintWriter pw) {
         pw.println("\nAudio mode: ");
         pw.println("- Requested mode = " + AudioSystem.modeToString(getMode()));
@@ -4560,7 +4602,9 @@
     private void setStreamVolume(int streamType, int index, int flags,
             @Nullable AudioDeviceAttributes ada,
             String callingPackage, String caller, String attributionTag, int uid,
-            boolean hasModifyAudioSettings) {
+            boolean hasModifyAudioSettings,
+            boolean canChangeMuteAndUpdateController) {
+
         if (DEBUG_VOL) {
             Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
                     + ", dev=" + ada
@@ -4663,7 +4707,7 @@
             onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
                     // ada is non-null when called from setDeviceVolume,
                     // which shouldn't update the mute state
-                    ada == null /*canChangeMute*/);
+                    canChangeMuteAndUpdateController /*canChangeMute*/);
             index = mStreamStates[streamType].getIndex(device);
         }
 
@@ -4673,7 +4717,7 @@
                 maybeSendSystemAudioStatusCommand(false);
             }
         }
-        if (ada == null) {
+        if (canChangeMuteAndUpdateController) {
             // only non-null when coming here from setDeviceVolume
             // TODO change test to check early if device is current device or not
             sendVolumeUpdate(streamType, oldIndex, index, flags, device);
@@ -5087,6 +5131,10 @@
     public int getStreamVolume(int streamType) {
         ensureValidStreamType(streamType);
         int device = getDeviceForStream(streamType);
+        return getStreamVolume(streamType, device);
+    }
+
+    private int getStreamVolume(int streamType, int device) {
         synchronized (VolumeStreamState.class) {
             int index = mStreamStates[streamType].getIndex(device);
 
@@ -6238,7 +6286,8 @@
 
         setStreamVolume(streamType, index, flags, /*device*/ null,
                 packageName, packageName, null, uid,
-                hasAudioSettingsPermission(uid, pid));
+                hasAudioSettingsPermission(uid, pid),
+                true /*canChangeMuteAndUpdateController*/);
     }
 
     //==========================================================================================
@@ -8781,7 +8830,7 @@
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
                                 mStreamVolumeAlias[mStreamType]);
                         AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
-                                mStreamType, mStreamVolumeAlias[mStreamType], index));
+                                mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex));
                         sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
                     }
                 }
@@ -10590,6 +10639,50 @@
         return anonymizeAudioDeviceAttributesUnchecked(ada);
     }
 
+    // ========================================================================================
+    // LoudnessCodecConfigurator
+
+    @Override
+    public void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) {
+        mLoudnessCodecHelper.registerLoudnessCodecUpdatesDispatcher(dispatcher);
+    }
+
+    @Override
+    public void unregisterLoudnessCodecUpdatesDispatcher(
+            ILoudnessCodecUpdatesDispatcher dispatcher) {
+        mLoudnessCodecHelper.unregisterLoudnessCodecUpdatesDispatcher(dispatcher);
+    }
+
+    /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+    @Override
+    public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+        mLoudnessCodecHelper.startLoudnessCodecUpdates(piid, codecInfoList);
+    }
+
+    /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+    @Override
+    public void stopLoudnessCodecUpdates(int piid) {
+        mLoudnessCodecHelper.stopLoudnessCodecUpdates(piid);
+    }
+
+    /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */
+    @Override
+    public void addLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+        mLoudnessCodecHelper.addLoudnessCodecInfo(piid, codecInfo);
+    }
+
+    /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */
+    @Override
+    public void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+        mLoudnessCodecHelper.removeLoudnessCodecInfo(piid, codecInfo);
+    }
+
+    /** @see LoudnessCodecConfigurator#getLoudnessCodecParams(AudioTrack, MediaCodec) */
+    @Override
+    public PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
+        return mLoudnessCodecHelper.getLoudnessParams(piid, codecInfo);
+    }
+
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
@@ -11385,6 +11478,7 @@
         } else {
             pw.println("\nMessage handler is null");
         }
+        dumpFlags(pw);
         mMediaFocusControl.dump(pw);
         dumpStreamStates(pw);
         dumpVolumeGroups(pw);
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 21a7d31..f69b9f6 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -151,11 +151,13 @@
         final int mStreamType;
         final int mAliasStreamType;
         final int mIndex;
+        final int mOldIndex;
 
-        VolChangedBroadcastEvent(int stream, int alias, int index) {
+        VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) {
             mStreamType = stream;
             mAliasStreamType = alias;
             mIndex = index;
+            mOldIndex = oldIndex;
         }
 
         @Override
@@ -163,7 +165,8 @@
             return new StringBuilder("sending VOLUME_CHANGED stream:")
                     .append(AudioSystem.streamToString(mStreamType))
                     .append(" index:").append(mIndex)
-                    .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType))
+                    .append(" (was:").append(mOldIndex)
+                    .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType))
                     .toString();
         }
     }
@@ -234,19 +237,35 @@
         final int mStream;
         final int mVal1;
         final int mVal2;
+        final int mVal3;
         final String mCaller;
         final String mGroupName;
 
+        /** used for VOL_SET_STREAM_VOL */
+        VolumeEvent(int op, int stream, int val1, int val2, int val3, String caller) {
+            mOp = op;
+            mStream = stream;
+            mVal1 = val1;
+            mVal2 = val2;
+            mVal3 = val3;
+            mCaller = caller;
+            // unused
+            mGroupName = null;
+            logMetricEvent();
+        }
+
         /** used for VOL_ADJUST_VOL_UID,
          *           VOL_ADJUST_SUGG_VOL,
          *           VOL_ADJUST_STREAM_VOL,
-         *           VOL_SET_STREAM_VOL */
+         */
         VolumeEvent(int op, int stream, int val1, int val2, String caller) {
             mOp = op;
             mStream = stream;
             mVal1 = val1;
             mVal2 = val2;
             mCaller = caller;
+            // unused
+            mVal3 = -1;
             mGroupName = null;
             logMetricEvent();
         }
@@ -257,6 +276,7 @@
             mVal1 = index;
             mVal2 = gainDb;
             // unused
+            mVal3 = -1;
             mStream = -1;
             mCaller = null;
             mGroupName = null;
@@ -269,6 +289,7 @@
             mVal1 = index;
             // unused
             mVal2 = 0;
+            mVal3 = -1;
             mStream = -1;
             mCaller = null;
             mGroupName = null;
@@ -282,6 +303,7 @@
             mVal1 = index;
             mVal2 = voiceActive ? 1 : 0;
             // unused
+            mVal3 = -1;
             mCaller = null;
             mGroupName = null;
             logMetricEvent();
@@ -294,6 +316,7 @@
             mVal1 = index;
             mVal2 = mode;
             // unused
+            mVal3 = -1;
             mCaller = null;
             mGroupName = null;
             logMetricEvent();
@@ -308,6 +331,8 @@
             mVal2 = flags;
             mCaller = caller;
             mGroupName = group;
+            // unused
+            mVal3 = -1;
             logMetricEvent();
         }
 
@@ -317,8 +342,10 @@
             mStream = stream;
             mVal1 = state ? 1 : 0;
             mVal2 = 0;
+            // unused
             mCaller = null;
             mGroupName = null;
+            mVal3 = -1;
             logMetricEvent();
         }
 
@@ -328,6 +355,8 @@
             mStream = -1;
             mVal1 = state ? 1 : 0;
             mVal2 = 0;
+            // unused
+            mVal3 = -1;
             mCaller = null;
             mGroupName = null;
             logMetricEvent();
@@ -386,6 +415,7 @@
                             .set(MediaMetrics.Property.EVENT, "setStreamVolume")
                             .set(MediaMetrics.Property.FLAGS, mVal2)
                             .set(MediaMetrics.Property.INDEX, mVal1)
+                            .set(MediaMetrics.Property.OLD_INDEX, mVal3)
                             .set(MediaMetrics.Property.STREAM_TYPE,
                                     AudioSystem.streamToString(mStream))
                             .record();
@@ -478,6 +508,7 @@
                             .append(AudioSystem.streamToString(mStream))
                             .append(" index:").append(mVal1)
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
+                            .append(" oldIndex:").append(mVal3)
                             .append(") from ").append(mCaller)
                             .toString();
                 case VOL_SET_HEARING_AID_VOL:
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
new file mode 100644
index 0000000..3c67e9d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
+import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.media.AudioDeviceInfo;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioSystem;
+import android.media.ILoudnessCodecUpdatesDispatcher;
+import android.media.LoudnessCodecInfo;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
+import android.os.Binder;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Class to handle the updates in loudness parameters and responsible to generate parameters that
+ * can be set directly on a MediaCodec.
+ */
+public class LoudnessCodecHelper {
+    private static final String TAG = "AS.LoudnessCodecHelper";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Property containing a string to set for a custom built in speaker SPL range as defined by
+     * CTA2075. The options that can be set are:
+     *   - "small": for max SPL with test signal < 75 dB,
+     *   - "medium": for max SPL with test signal between 70 and 90 dB,
+     *   - "large": for max SPL with test signal > 85 dB.
+     */
+    private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE =
+            "audio.loudness.builtin-speaker-spl-range-size";
+
+    private static final int SPL_RANGE_UNKNOWN = 0;
+    private static final int SPL_RANGE_SMALL = 1;
+    private static final int SPL_RANGE_MEDIUM = 2;
+    private static final int SPL_RANGE_LARGE = 3;
+
+    /** The possible transducer SPL ranges as defined in CTA2075 */
+    @IntDef({
+            SPL_RANGE_UNKNOWN,
+            SPL_RANGE_SMALL,
+            SPL_RANGE_MEDIUM,
+            SPL_RANGE_LARGE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceSplRange {}
+
+    private static final class LoudnessRemoteCallbackList extends
+            RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> {
+        private final LoudnessCodecHelper mLoudnessCodecHelper;
+        LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) {
+            mLoudnessCodecHelper = loudnessCodecHelper;
+        }
+
+        @Override
+        public void onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie) {
+            Integer pid = null;
+            if (cookie instanceof Integer) {
+                pid = (Integer) cookie;
+            }
+            if (pid != null) {
+                mLoudnessCodecHelper.removePid(pid);
+            }
+            super.onCallbackDied(callback, cookie);
+        }
+    }
+
+    private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers =
+            new LoudnessRemoteCallbackList(this);
+
+    private final Object mLock = new Object();
+
+    /** Contains for each started piid the set corresponding to unique registered audio codecs. */
+    @GuardedBy("mLock")
+    private final SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>();
+
+    /** Contains the current device id assignment for each piid. */
+    @GuardedBy("mLock")
+    private final SparseIntArray mPiidToDeviceIdCache = new SparseIntArray();
+
+    /** Maps each piid to the owner process of the player. */
+    @GuardedBy("mLock")
+    private final SparseIntArray mPiidToPidCache = new SparseIntArray();
+
+    private final AudioService mAudioService;
+
+    /** Contains the properties necessary to compute the codec loudness related parameters. */
+    private static final class LoudnessCodecInputProperties {
+        private final int mMetadataType;
+
+        private final boolean mIsDownmixing;
+
+        @DeviceSplRange
+        private final int mDeviceSplRange;
+
+        static final class Builder {
+            private int mMetadataType;
+
+            private boolean mIsDownmixing;
+
+            @DeviceSplRange
+            private int mDeviceSplRange;
+
+            Builder setMetadataType(int metadataType) {
+                mMetadataType = metadataType;
+                return this;
+            }
+            Builder setIsDownmixing(boolean isDownmixing) {
+                mIsDownmixing = isDownmixing;
+                return this;
+            }
+            Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) {
+                mDeviceSplRange = deviceSplRange;
+                return this;
+            }
+
+            LoudnessCodecInputProperties build() {
+                return new LoudnessCodecInputProperties(mMetadataType,
+                        mIsDownmixing, mDeviceSplRange);
+            }
+        }
+
+        private LoudnessCodecInputProperties(int metadataType,
+                                             boolean isDownmixing,
+                                             @DeviceSplRange int deviceSplRange) {
+            mMetadataType = metadataType;
+            mIsDownmixing = isDownmixing;
+            mDeviceSplRange = deviceSplRange;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            // type check and cast
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final LoudnessCodecInputProperties lcip = (LoudnessCodecInputProperties) obj;
+            return mMetadataType == lcip.mMetadataType
+                    && mIsDownmixing == lcip.mIsDownmixing
+                    && mDeviceSplRange == lcip.mDeviceSplRange;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mMetadataType, mIsDownmixing, mDeviceSplRange);
+        }
+
+        @Override
+        public String toString() {
+            return "Loudness properties:"
+                    + " device SPL range: " + splRangeToString(mDeviceSplRange)
+                    + " down-mixing: " + mIsDownmixing
+                    + " metadata type: " + mMetadataType;
+        }
+
+        PersistableBundle createLoudnessParameters() {
+            // TODO: create bundle with new parameters
+            return new PersistableBundle();
+        }
+
+    }
+
+    @GuardedBy("mLock")
+    private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties =
+            new HashMap<>();
+
+    LoudnessCodecHelper(@NonNull AudioService audioService) {
+        mAudioService = Objects.requireNonNull(audioService);
+    }
+
+    void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) {
+        mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
+    }
+
+    void unregisterLoudnessCodecUpdatesDispatcher(
+            ILoudnessCodecUpdatesDispatcher dispatcher) {
+        mLoudnessUpdateDispatchers.unregister(dispatcher);
+    }
+
+    void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+        if (DEBUG) {
+            Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList);
+        }
+        Set<LoudnessCodecInfo> infoSet;
+        synchronized (mLock) {
+            if (mStartedPiids.contains(piid)) {
+                Log.w(TAG, "Already started loudness updates for piid " + piid);
+                return;
+            }
+            infoSet = new HashSet<>(codecInfoList);
+            mStartedPiids.put(piid, infoSet);
+
+            mPiidToPidCache.put(piid, Binder.getCallingPid());
+        }
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            mAudioService.getActivePlaybackConfigurations().stream().filter(
+                    conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
+                            apc -> updateCodecParametersForConfiguration(apc, infoSet));
+        }
+    }
+
+    void stopLoudnessCodecUpdates(int piid) {
+        if (DEBUG) {
+            Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid);
+        }
+        synchronized (mLock) {
+            if (!mStartedPiids.contains(piid)) {
+                Log.w(TAG, "Loudness updates are already stopped for piid " + piid);
+                return;
+            }
+            mStartedPiids.remove(piid);
+            mPiidToDeviceIdCache.delete(piid);
+            mPiidToPidCache.delete(piid);
+        }
+    }
+
+    void addLoudnessCodecInfo(int piid, LoudnessCodecInfo info) {
+        if (DEBUG) {
+            Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " info " + info);
+        }
+
+        Set<LoudnessCodecInfo> infoSet;
+        synchronized (mLock) {
+            if (!mStartedPiids.contains(piid)) {
+                Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid);
+                return;
+            }
+
+            infoSet = mStartedPiids.get(piid);
+            infoSet.add(info);
+        }
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            mAudioService.getActivePlaybackConfigurations().stream().filter(
+                    conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
+                            apc -> updateCodecParametersForConfiguration(apc, Set.of(info)));
+        }
+    }
+
+    void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+        if (DEBUG) {
+            Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo);
+        }
+        synchronized (mLock) {
+            if (!mStartedPiids.contains(piid)) {
+                Log.w(TAG, "Cannot remove loudness info for stopped piid " + piid);
+                return;
+            }
+            final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid);
+            infoSet.remove(codecInfo);
+        }
+    }
+
+    void removePid(int pid) {
+        if (DEBUG) {
+            Log.d(TAG, "Removing pid " + pid + " from receiving updates");
+        }
+        synchronized (mLock) {
+            for (int i = 0; i < mPiidToPidCache.size(); ++i) {
+                int piid = mPiidToPidCache.keyAt(i);
+                if (mPiidToPidCache.get(piid) == pid) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing piid  " + piid);
+                    }
+                    mStartedPiids.delete(piid);
+                    mPiidToDeviceIdCache.delete(piid);
+                }
+            }
+        }
+    }
+
+    PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
+        if (DEBUG) {
+            Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo);
+        }
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            final List<AudioPlaybackConfiguration> configs =
+                    mAudioService.getActivePlaybackConfigurations();
+
+            for (final AudioPlaybackConfiguration apc : configs) {
+                if (apc.getPlayerInterfaceId() == piid) {
+                    final AudioDeviceInfo info = apc.getAudioDeviceInfo();
+                    if (info == null) {
+                        Log.i(TAG, "Player with piid " + piid + " is not assigned any device");
+                        break;
+                    }
+                    synchronized (mLock) {
+                        return getCodecBundle_l(info, codecInfo);
+                    }
+                }
+            }
+        }
+
+        // return empty Bundle
+        return new PersistableBundle();
+    }
+
+    /** Method to be called whenever there is a changed in the active playback configurations. */
+    void updateCodecParameters(List<AudioPlaybackConfiguration> configs) {
+        if (DEBUG) {
+            Log.d(TAG, "updateCodecParameters: configs " + configs);
+        }
+
+        List<AudioPlaybackConfiguration> updateApcList = new ArrayList<>();
+        synchronized (mLock) {
+            for (final AudioPlaybackConfiguration apc : configs) {
+                int piid = apc.getPlayerInterfaceId();
+                int cachedDeviceId = mPiidToDeviceIdCache.get(piid, PLAYER_DEVICEID_INVALID);
+                AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
+                if (deviceInfo == null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "No device info for piid: " + piid);
+                    }
+                    if (cachedDeviceId != PLAYER_DEVICEID_INVALID) {
+                        mPiidToDeviceIdCache.delete(piid);
+                        if (DEBUG) {
+                            Log.d(TAG, "Remove cached device id for piid: " + piid);
+                        }
+                    }
+                    continue;
+                }
+                if (cachedDeviceId == deviceInfo.getId()) {
+                    // deviceId did not change
+                    if (DEBUG) {
+                        Log.d(TAG, "DeviceId " + cachedDeviceId + " for piid: " + piid
+                                + " did not change");
+                    }
+                    continue;
+                }
+                mPiidToDeviceIdCache.put(piid, deviceInfo.getId());
+                if (mStartedPiids.contains(piid)) {
+                    updateApcList.add(apc);
+                }
+            }
+        }
+
+        updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc, null));
+    }
+
+    /** Updates and dispatches the new loudness parameters for the {@code codecInfos} set.
+     *
+     * @param apc the player configuration for which the loudness parameters are updated.
+     * @param codecInfos the codec info for which the parameters are updated. If {@code null},
+     *                   send updates for all the started codecs assigned to {@code apc}
+     */
+    private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc,
+            Set<LoudnessCodecInfo> codecInfos) {
+        if (DEBUG) {
+            Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc + " codecInfos: "
+                    + codecInfos);
+        }
+        final PersistableBundle allBundles = new PersistableBundle();
+        final int piid = apc.getPlayerInterfaceId();
+        synchronized (mLock) {
+            if (codecInfos == null) {
+                codecInfos = mStartedPiids.get(piid);
+            }
+
+            final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
+            if (codecInfos != null && deviceInfo != null) {
+                for (LoudnessCodecInfo info : codecInfos) {
+                    allBundles.putPersistableBundle(Integer.toString(info.mediaCodecHashCode),
+                            getCodecBundle_l(deviceInfo, info));
+                }
+            }
+        }
+
+        if (!allBundles.isDefinitelyEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "Dispatching for piid: " + piid + " bundle: " + allBundles);
+            }
+            dispatchNewLoudnessParameters(piid, allBundles);
+        }
+    }
+
+    private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) {
+        if (DEBUG) {
+            Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid);
+        }
+        final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
+        for (int i = 0; i < nbDispatchers; ++i) {
+            try {
+                mLoudnessUpdateDispatchers.getBroadcastItem(i)
+                        .dispatchLoudnessCodecParameterChange(piid, bundle);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error dispatching for piid: " + piid + " bundle: " + bundle , e);
+            }
+        }
+        mLoudnessUpdateDispatchers.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private PersistableBundle getCodecBundle_l(AudioDeviceInfo deviceInfo,
+                                             LoudnessCodecInfo codecInfo) {
+        LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder();
+        LoudnessCodecInputProperties prop = builder.setDeviceSplRange(getDeviceSplRange(deviceInfo))
+                .setIsDownmixing(codecInfo.isDownmixing)
+                .setMetadataType(codecInfo.metadataType)
+                .build();
+
+        if (mCachedProperties.containsKey(prop)) {
+            return mCachedProperties.get(prop);
+        }
+        final PersistableBundle codecBundle = prop.createLoudnessParameters();
+        mCachedProperties.put(prop, codecBundle);
+        return codecBundle;
+    }
+
+    @DeviceSplRange
+    private int getDeviceSplRange(AudioDeviceInfo deviceInfo) {
+        final int internalDeviceType = deviceInfo.getInternalType();
+        if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) {
+            final String splRange = SystemProperties.get(
+                    SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown");
+            if (!splRange.equals("unknown")) {
+                return stringToSplRange(splRange);
+            }
+
+            @DeviceSplRange int result = SPL_RANGE_SMALL;  // default for phone/tablet/watch
+            if (mAudioService.isPlatformAutomotive() || mAudioService.isPlatformTelevision()) {
+                result = SPL_RANGE_MEDIUM;
+            }
+
+            return result;
+        } else if (internalDeviceType == AudioSystem.DEVICE_OUT_USB_HEADSET
+                || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET
+                || (AudioSystem.isBluetoothDevice(internalDeviceType)
+                && mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress(),
+                AudioSystem.isBluetoothLeDevice(internalDeviceType))
+                == AUDIO_DEVICE_CATEGORY_HEADPHONES)) {
+            return SPL_RANGE_LARGE;
+        } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) {
+            final int audioDeviceType = mAudioService.getBluetoothAudioDeviceCategory(
+                    deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType));
+            if (audioDeviceType == AUDIO_DEVICE_CATEGORY_CARKIT) {
+                return SPL_RANGE_MEDIUM;
+            } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_WATCH) {
+                return SPL_RANGE_SMALL;
+            } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_HEARING_AID) {
+                return SPL_RANGE_SMALL;
+            }
+        }
+
+        return SPL_RANGE_UNKNOWN;
+    }
+
+    private static String splRangeToString(@DeviceSplRange int splRange) {
+        switch (splRange) {
+            case SPL_RANGE_LARGE: return "large";
+            case SPL_RANGE_MEDIUM: return "medium";
+            case SPL_RANGE_SMALL: return "small";
+            default: return "unknown";
+        }
+    }
+
+    @DeviceSplRange
+    private static int stringToSplRange(String splRange) {
+        switch (splRange) {
+            case "large": return SPL_RANGE_LARGE;
+            case "medium": return SPL_RANGE_MEDIUM;
+            case "small": return SPL_RANGE_SMALL;
+            default: return SPL_RANGE_UNKNOWN;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 793752f..c72632f 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -1297,7 +1297,8 @@
         }
         final int index = safeMediaVolumeIndex(nativeDeviceType);
         mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada,
-                mContext.getOpPackageName(), /*attributionTag=*/null);
+                mContext.getOpPackageName(), /*attributionTag=*/null,
+                true /*canChangeMuteAndUpdateController*/);
     }
 
     // StreamVolumeCommand contains the information needed to defer the process of
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index ea92154..61e4f36 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -19,6 +19,9 @@
 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
 import static android.media.AudioSystem.isBluetoothDevice;
+import static android.media.AudioSystem.isBluetoothLeDevice;
+
+import static com.android.media.audio.Flags.dsaOverBtLeAudio;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1625,10 +1628,10 @@
     }
 
     private int getHeadSensorHandleUpdateTracker() {
-        int headHandle = -1;
+        Sensor htSensor = null;
         if (sRoutingDevices.isEmpty()) {
             logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
-            return headHandle;
+            return  -1;
         }
         final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
         List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
@@ -1642,27 +1645,86 @@
         for (String address : deviceAddresses) {
             UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes(
                     new AudioDeviceAttributes(currentDevice.getInternalType(), address));
-            for (Sensor sensor : sensors) {
-                final UUID uuid = sensor.getUuid();
-                if (uuid.equals(routingDeviceUuid)) {
-                    headHandle = sensor.getHandle();
-                    if (!setHasHeadTracker(currentDevice)) {
-                        headHandle = -1;
+            if (dsaOverBtLeAudio()) {
+                for (Sensor sensor : sensors) {
+                    final UUID uuid = sensor.getUuid();
+                    if (uuid.equals(routingDeviceUuid)) {
+                        htSensor = sensor;
+                        HeadtrackerInfo info = new HeadtrackerInfo(sensor);
+                        if (isBluetoothLeDevice(currentDevice.getInternalType())) {
+                            if (info.getMajorVersion() == 2) {
+                                // Version 2 is used only by LE Audio profile
+                                break;
+                            }
+                            // we do not break, as this could be a match on the A2DP sensor
+                            // for a dual mode headset.
+                        } else if (info.getMajorVersion() == 1) {
+                            // Version 1 is used only by A2DP profile
+                            break;
+                        }
                     }
+                    if (htSensor == null && uuid.equals(UuidUtils.STANDALONE_UUID)) {
+                        htSensor = sensor;
+                        // we do not break, perhaps we find a head tracker on device.
+                    }
+                }
+                if (htSensor != null) {
+                    if (htSensor.getUuid().equals(UuidUtils.STANDALONE_UUID)) {
+                        break;
+                    }
+                    if (setHasHeadTracker(currentDevice)) {
+                        break;
+                    } else {
+                        htSensor = null;
+                    }
+                }
+            } else {
+                for (Sensor sensor : sensors) {
+                    final UUID uuid = sensor.getUuid();
+                    if (uuid.equals(routingDeviceUuid)) {
+                        htSensor = sensor;
+                        if (!setHasHeadTracker(currentDevice)) {
+                            htSensor = null;
+                        }
+                        break;
+                    }
+                    if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+                        htSensor = sensor;
+                        // we do not break, perhaps we find a head tracker on device.
+                    }
+                }
+                if (htSensor != null) {
                     break;
                 }
-                if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
-                    headHandle = sensor.getHandle();
-                    // we do not break, perhaps we find a head tracker on device.
-                }
-            }
-            if (headHandle != -1) {
-                break;
             }
         }
-        return headHandle;
+        return htSensor != null ? htSensor.getHandle() : -1;
     }
 
+    /**
+     * Contains the information parsed from the head tracker sensor version.
+     * See platform/hardware/libhardware/modules/sensors/dynamic_sensor/HidRawSensor.h
+     * for the definition of version and capability fields.
+     */
+    private static class HeadtrackerInfo {
+        private final int mVersion;
+        HeadtrackerInfo(Sensor sensor) {
+            mVersion = sensor.getVersion();
+        }
+        int getMajorVersion() {
+            return (mVersion & 0xFF000000) >> 24;
+        }
+        int getMinorVersion() {
+            return (mVersion & 0xFF0000) >> 16;
+        }
+        boolean hasAclTransport() {
+            return getMajorVersion() == 2 ? ((mVersion & 0x1) != 0) : false;
+        }
+        boolean hasIsoTransport() {
+            return getMajorVersion() == 2 ? ((mVersion & 0x2) != 0) : false;
+        }
+    };
+
     private int getScreenSensorHandle() {
         int screenHandle = -1;
         Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index b9ccbfb..c507300 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -576,7 +576,7 @@
     }
 
     void onDialogAnimatedIn(boolean startFingerprintNow) {
-        if (mState != STATE_AUTH_STARTED) {
+        if (mState != STATE_AUTH_STARTED && mState != STATE_ERROR_PENDING_SYSUI) {
             Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
             return;
         }
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index c629b2b..cecde55 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -149,6 +149,14 @@
     public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
 
     /**
+     * Returns the ID of the device which owns the display with the given ID.
+     *
+     * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then
+     * {@link android.content.Context#DEVICE_ID_DEFAULT} is returned.</p>
+     */
+    public abstract int getDeviceIdForDisplayId(int displayId);
+
+    /**
      * Gets the persistent ID for the VirtualDevice with the given device ID.
      *
      * @param deviceId which device we're asking about
@@ -157,4 +165,10 @@
      * @see VirtualDevice#getPersistentDeviceId()
      */
     public abstract @Nullable String getPersistentIdForDevice(int deviceId);
+
+    /**
+     * Returns all current persistent device IDs, including the ones for which no virtual device
+     * exists, as long as one may have existed or can be created.
+     */
+    public abstract @NonNull Set<String> getAllPersistentDeviceIds();
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index a12243b..aef2248 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2549,15 +2549,14 @@
      * secondary thread to perform connection work, returning quickly.
      *
      * Should only be called to respond to Binder requests as this enforces caller permission. Use
-     * {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the
+     * {@link #startLegacyVpnPrivileged(VpnProfile)} to skip the
      * permission check only when the caller is trusted (or the call is initiated by the system).
      */
-    public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying,
-            LinkProperties egress) {
+    public void startLegacyVpn(VpnProfile profile) {
         enforceControlPermission();
         final long token = Binder.clearCallingIdentity();
         try {
-            startLegacyVpnPrivileged(profile, underlying, egress);
+            startLegacyVpnPrivileged(profile);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2616,13 +2615,13 @@
     }
 
     /**
-     * Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not
-     * check permissions under the assumption that the caller is the system.
+     * Like {@link #startLegacyVpn(VpnProfile)}, but does not check permissions under
+     * the assumption that the caller is the system.
      *
      * Callers are responsible for checking permissions if needed.
      */
-    public void startLegacyVpnPrivileged(VpnProfile profile,
-            @Nullable Network underlying, @NonNull LinkProperties egress) {
+    public void startLegacyVpnPrivileged(VpnProfile profileToStart) {
+        final VpnProfile profile = profileToStart.clone();
         UserInfo user = mUserManager.getUserInfo(mUserId);
         if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
                     new UserHandle(mUserId))) {
@@ -3385,6 +3384,13 @@
          *              given network to start a new IKE session.
          */
         private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
+            synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+                setVpnNetworkPreference(mSessionKey,
+                        createUserAndRestrictedProfilesRanges(mUserId,
+                                mConfig.allowedApplications, mConfig.disallowedApplications));
+            }
             if (underlyingNetwork == null) {
                 // For null underlyingNetwork case, there will not be a NetworkAgent available so
                 // no underlying network update is necessary here. Note that updating
@@ -3905,6 +3911,7 @@
                 updateState(DetailedState.FAILED, exception.getMessage());
             }
 
+            clearVpnNetworkPreference(mSessionKey);
             disconnectVpnRunner();
         }
 
@@ -4039,6 +4046,13 @@
             }
 
             resetIkeState();
+            if (errorCode != VpnManager.ERROR_CODE_NETWORK_LOST
+                    // Clear the VPN network preference when the retry delay is higher than 5s.
+                    // mRetryCount was increased when scheduleRetryNewIkeSession() is called,
+                    // therefore use mRetryCount - 1 here.
+                    && mDeps.getNextRetryDelayMs(mRetryCount - 1) > 5_000L) {
+                clearVpnNetworkPreference(mSessionKey);
+            }
         }
 
         /**
@@ -4085,13 +4099,17 @@
             mCarrierConfigManager.unregisterCarrierConfigChangeListener(
                     mCarrierConfigChangeListener);
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-            clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
         }
 
         @Override
         public void exitVpnRunner() {
+            // mSessionKey won't be changed since the Ikev2VpnRunner is created, so it's ok to use
+            // it outside the mExecutor. And clearing the VPN network preference here can prevent
+            // the case that the VPN network preference isn't cleared when Ikev2VpnRunner became
+            // stale.
+            clearVpnNetworkPreference(mSessionKey);
             try {
                 mExecutor.execute(() -> {
                     disconnectVpnRunner();
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 1da7f0c..cd3f0f0 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.app.job.JobParameters;
 import android.app.job.JobService;
+import android.content.pm.PackageManagerInternal;
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.Log;
@@ -28,6 +29,7 @@
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
 
 public class SyncJobService extends JobService {
     private static final String TAG = "SyncManager";
@@ -97,6 +99,20 @@
             return true;
         }
 
+        // TODO(b/209852664): remove this logic from here once it's added within JobScheduler.
+        // JobScheduler should not call onStartJob for syncs whose source packages are stopped.
+        // Until JS adds the relevant logic, this is a temporary solution to keep deferring syncs
+        // for packages in the stopped state.
+        if (android.content.pm.Flags.stayStopped()) {
+            if (LocalServices.getService(PackageManagerInternal.class)
+                    .isPackageStopped(op.owningPackage, op.target.userId)) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Slog.d(TAG, "Skipping sync for force-stopped package: " + op.owningPackage);
+                }
+                return false;
+            }
+        }
+
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
         synchronized (sLock) {
             final int jobId = params.getJobId();
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index fac727f..dff14b5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -1114,12 +1114,22 @@
 
         public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) {
             mHandler.post(() -> {
+                boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER);
+                if (tracingEnabled) { // To avoid creating the string when not needed.
+                    Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+                            "notifyDeviceStateInfoAsync(pid=" + mPid + ")");
+                }
                 try {
                     mCallback.onDeviceStateInfoChanged(info);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
                             ex);
                 }
+                finally {
+                    if (tracingEnabled) {
+                        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+                    }
+                }
             });
         }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index af33de0..50ab3f8 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -63,13 +63,27 @@
      */
     int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5;
 
+    /**
+     * Indicating that the supported device states have changed because an external display was
+     * added.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED = 6;
+
+    /**
+     * Indicating that the supported device states have changed because an external display was
+     * removed.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED = 7;
+
     @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
             SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
             SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
             SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
             SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL,
             SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED,
-            SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED
+            SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED,
+            SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED,
+            SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface SupportedStatesUpdatedReason {}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 99a5398..d848f4b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -120,6 +121,7 @@
 
         // Display independent, mode dependent values
         float[] brightnessLevelsNits;
+        float[] brightnessLevels = null;
         float[] luxLevels;
         if (isForIdleMode) {
             brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
@@ -129,11 +131,21 @@
         } else {
             brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
             luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
+
+            brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
+            if (brightnessLevels == null || brightnessLevels.length == 0) {
+                // Load the old configuration in the range [0, 255]. The values need to be
+                // normalized to the range [0, 1].
+                int[] brightnessLevelsInt = resources.getIntArray(
+                        com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+                brightnessLevels = new float[brightnessLevelsInt.length];
+                for (int i = 0; i < brightnessLevels.length; i++) {
+                    brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]);
+                }
+            }
         }
 
         // Display independent, mode independent values
-        int[] brightnessLevelsBacklight = resources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
         float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
                 1, 1);
@@ -154,8 +166,8 @@
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
                     autoBrightnessAdjustmentMaxGamma, isForIdleMode, displayWhiteBalanceController);
-        } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
-            return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
+        } else if (isValidMapping(luxLevels, brightnessLevels)) {
+            return new SimpleMappingStrategy(luxLevels, brightnessLevels,
                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
         } else {
             return null;
@@ -619,7 +631,7 @@
         private float mUserBrightness;
         private long mShortTermModelTimeout;
 
-        private SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma,
+        private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma,
                 long timeout) {
             Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
                     "Lux and brightness arrays must not be empty!");
@@ -634,7 +646,7 @@
             mBrightness = new float[N];
             for (int i = 0; i < N; i++) {
                 mLux[i] = lux[i];
-                mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
+                mBrightness[i] = brightness[i];
             }
 
             mMaxGamma = maxGamma;
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 70d4ad2..c26118e 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -20,6 +20,8 @@
 import android.os.Handler;
 import android.view.Display;
 
+import com.android.server.display.feature.DisplayManagerFlags;
+
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -39,6 +41,7 @@
     private final Handler mHandler;
     private final Listener mListener;
     private final String mName;
+    private final DisplayManagerFlags mFeatureFlags;
 
     public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
     public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
@@ -50,13 +53,14 @@
     private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1);  // 0 = no mode.
 
     // Called with SyncRoot lock held.
-    public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener, String name) {
+    DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
+            Listener listener, String name, DisplayManagerFlags featureFlags) {
         mSyncRoot = syncRoot;
         mContext = context;
         mHandler = handler;
         mListener = listener;
         mName = name;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -88,6 +92,10 @@
         return mName;
     }
 
+    public final DisplayManagerFlags getFeatureFlags() {
+        return mFeatureFlags;
+    }
+
     /**
      * Registers the display adapter with the display manager.
      *
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 6695801..9fcaa1e 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -38,6 +38,7 @@
     private final boolean mShouldUseAutoBrightness;
 
     private final boolean mIsSlowChange;
+    private final boolean mShouldUpdateScreenBrightnessSetting;
 
     private final float mCustomAnimationRate;
 
@@ -50,6 +51,7 @@
         mIsSlowChange = builder.isSlowChange();
         mMaxBrightness = builder.getMaxBrightness();
         mCustomAnimationRate = builder.getCustomAnimationRate();
+        mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
     }
 
     /**
@@ -109,6 +111,13 @@
         return mCustomAnimationRate;
     }
 
+    /**
+     * @return {@code true} if the screen brightness setting should be updated
+     */
+    public boolean shouldUpdateScreenBrightnessSetting() {
+        return mShouldUpdateScreenBrightnessSetting;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -123,6 +132,8 @@
         stringBuilder.append("\n    isSlowChange:").append(mIsSlowChange);
         stringBuilder.append("\n    maxBrightness:").append(mMaxBrightness);
         stringBuilder.append("\n    customAnimationRate:").append(mCustomAnimationRate);
+        stringBuilder.append("\n    shouldUpdateScreenBrightnessSetting:")
+                .append(mShouldUpdateScreenBrightnessSetting);
         return stringBuilder.toString();
     }
 
@@ -149,13 +160,16 @@
                 && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
                 && mIsSlowChange == otherState.isSlowChange()
                 && mMaxBrightness == otherState.getMaxBrightness()
-                && mCustomAnimationRate == otherState.getCustomAnimationRate();
+                && mCustomAnimationRate == otherState.getCustomAnimationRate()
+                && mShouldUpdateScreenBrightnessSetting
+                    == otherState.shouldUpdateScreenBrightnessSetting();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
-                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
+                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate,
+                mShouldUpdateScreenBrightnessSetting);
     }
 
     /**
@@ -177,6 +191,7 @@
         private boolean mIsSlowChange;
         private float mMaxBrightness;
         private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
+        private boolean mShouldUpdateScreenBrightnessSetting;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -194,6 +209,8 @@
             builder.setIsSlowChange(state.isSlowChange());
             builder.setMaxBrightness(state.getMaxBrightness());
             builder.setCustomAnimationRate(state.getCustomAnimationRate());
+            builder.setShouldUpdateScreenBrightnessSetting(
+                    state.shouldUpdateScreenBrightnessSetting());
             return builder;
         }
 
@@ -290,8 +307,8 @@
         /**
          * See {@link DisplayBrightnessState#isSlowChange()}.
          */
-        public Builder setIsSlowChange(boolean shouldUseAutoBrightness) {
-            this.mIsSlowChange = shouldUseAutoBrightness;
+        public Builder setIsSlowChange(boolean isSlowChange) {
+            this.mIsSlowChange = isSlowChange;
             return this;
         }
 
@@ -334,6 +351,22 @@
         }
 
         /**
+         * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}.
+         */
+        public boolean shouldUpdateScreenBrightnessSetting() {
+            return mShouldUpdateScreenBrightnessSetting;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}.
+         */
+        public Builder setShouldUpdateScreenBrightnessSetting(
+                boolean shouldUpdateScreenBrightnessSetting) {
+            mShouldUpdateScreenBrightnessSetting = shouldUpdateScreenBrightnessSetting;
+            return this;
+        }
+
+        /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
         public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 9f4f787..3b05b47 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -209,7 +209,7 @@
             int state,
             float brightnessState,
             float sdrBrightnessState,
-            @Nullable DisplayOffloadSession displayOffloadSession) {
+            @Nullable DisplayOffloadSessionImpl displayOffloadSession) {
         return null;
     }
 
@@ -400,6 +400,7 @@
     }
 
     private DisplayDeviceConfig loadDisplayDeviceConfig() {
-        return DisplayDeviceConfig.create(mContext, false);
+        return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
+                mDisplayAdapter.getFeatureFlags());
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index b99de5c..d97127c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -57,6 +57,7 @@
 import com.android.server.display.config.HighBrightnessMode;
 import com.android.server.display.config.IntegerArray;
 import com.android.server.display.config.LuxThrottling;
+import com.android.server.display.config.LuxToBrightnessMapping;
 import com.android.server.display.config.NitsMap;
 import com.android.server.display.config.NonNegativeFloatToFloatPoint;
 import com.android.server.display.config.Point;
@@ -77,6 +78,7 @@
 import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.UsiVersion;
 import com.android.server.display.config.XmlParser;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.DebugUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -310,16 +312,18 @@
  *          <darkeningLightDebounceIdleMillis>
  *              1000
  *          </darkeningLightDebounceIdleMillis>
- *          <displayBrightnessMapping>
- *              <displayBrightnessPoint>
- *                  <lux>50</lux>
- *                  <nits>45.32</nits>
- *              </displayBrightnessPoint>
- *              <displayBrightnessPoint>
- *                  <lux>80</lux>
- *                  <nits>75.43</nits>
- *              </displayBrightnessPoint>
- *          </displayBrightnessMapping>
+ *          <luxToBrightnessMapping>
+ *            <map>
+ *              <point>
+ *                <first>0</first>
+ *                <second>0.2</second>
+ *              </point>
+ *              <point>
+ *                <first>80</first>
+ *                <second>0.3</second>
+ *              </point>
+ *            </map>
+ *          </luxToBrightnessMapping>
  *      </autoBrightness>
  *
  *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -528,6 +532,7 @@
  *         <majorVersion>2</majorVersion>
  *         <minorVersion>0</minorVersion>
  *     </usiVersion>
+ *     <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -629,7 +634,6 @@
     // for the corresponding values above
     private float[] mBrightness;
 
-
     /**
      * Array of desired screen brightness in nits corresponding to the lux values
      * in the mBrightnessLevelsLux array. The display brightness is defined as the
@@ -639,20 +643,25 @@
     private float[] mBrightnessLevelsNits;
 
     /**
-     * Array of light sensor lux values to define our levels for auto backlight
-     * brightness support.
+     * Array of desired screen brightness corresponding to the lux values
+     * in the mBrightnessLevelsLux array. The brightness values must be non-negative and
+     * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and
+     * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays
+     */
+    private float[] mBrightnessLevels;
+
+    /**
+     * Array of light sensor lux values to define our levels for auto-brightness support.
      *
-     * The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits,
-     * with first value always being 0 lux
+     * The first lux value is always 0.
      *
-     * The control points must be strictly increasing.  Each control point
-     * corresponds to an entry in the brightness backlight values arrays.
-     * For example, if lux == level[1] (second element of the levels array)
-     * then the brightness will be determined by value[0] (first element
-     * of the brightness values array).
+     * The control points must be strictly increasing. Each control point corresponds to an entry
+     * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
+     * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
+     * element of the brightness values array).
      *
-     * Spline interpolation is used to determine the auto-brightness
-     * backlight values for lux levels between these control points.
+     * Spline interpolation is used to determine the auto-brightness values for lux levels between
+     * these control points.
      */
     private float[] mBrightnessLevelsLux;
 
@@ -843,9 +852,17 @@
     @Nullable
     private HdrBrightnessData mHdrBrightnessData;
 
+    /**
+     * Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+     */
+    private float mBrightnessCapForWearBedtimeMode;
+
+    private final DisplayManagerFlags mFlags;
+
     @VisibleForTesting
-    DisplayDeviceConfig(Context context) {
+    DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
         mContext = context;
+        mFlags = flags;
     }
 
     /**
@@ -861,9 +878,9 @@
      * @return A configuration instance for the specified display.
      */
     public static DisplayDeviceConfig create(Context context, long physicalDisplayId,
-            boolean isFirstDisplay) {
+            boolean isFirstDisplay, DisplayManagerFlags flags) {
         final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId,
-                isFirstDisplay);
+                isFirstDisplay, flags);
 
         config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context));
         return config;
@@ -878,28 +895,29 @@
      *                     or the default values.
      * @return A configuration instance.
      */
-    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
+    public static DisplayDeviceConfig create(Context context, boolean useConfigXml,
+            DisplayManagerFlags flags) {
         final DisplayDeviceConfig config;
         if (useConfigXml) {
-            config = getConfigFromGlobalXml(context);
+            config = getConfigFromGlobalXml(context, flags);
         } else {
-            config = getConfigFromPmValues(context);
+            config = getConfigFromPmValues(context, flags);
         }
         return config;
     }
 
     private static DisplayDeviceConfig createWithoutDefaultValues(Context context,
-            long physicalDisplayId, boolean isFirstDisplay) {
+            long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
         DisplayDeviceConfig config;
 
         config = loadConfigFromDirectory(context, Environment.getProductDirectory(),
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
 
         config = loadConfigFromDirectory(context, Environment.getVendorDirectory(),
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
@@ -907,7 +925,7 @@
         // If no config can be loaded from any ddc xml at all,
         // prepare a whole config using the global config.xml.
         // Guaranteed not null
-        return create(context, isFirstDisplay);
+        return create(context, isFirstDisplay, flags);
     }
 
     private static DisplayConfiguration loadDefaultConfigurationXml(Context context) {
@@ -960,18 +978,19 @@
     }
 
     private static DisplayDeviceConfig loadConfigFromDirectory(Context context,
-            File baseDirectory, long physicalDisplayId) {
+            File baseDirectory, long physicalDisplayId, DisplayManagerFlags flags) {
         DisplayDeviceConfig config;
         // Create config using filename from physical ID (including "stable" bit).
         config = getConfigFromSuffix(context, baseDirectory, STABLE_ID_SUFFIX_FORMAT,
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
 
         // Create config using filename from physical ID (excluding "stable" bit).
         final long withoutStableFlag = physicalDisplayId & ~STABLE_FLAG;
-        config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag);
+        config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag,
+                flags);
         if (config != null) {
             return config;
         }
@@ -980,7 +999,7 @@
         final DisplayAddress.Physical physicalAddress =
                 DisplayAddress.fromPhysicalDisplayId(physicalDisplayId);
         int port = physicalAddress.getPort();
-        config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port);
+        config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port, flags);
         return config;
     }
 
@@ -1599,6 +1618,13 @@
     }
 
     /**
+     * @return Auto brightness brightening levels
+     */
+    public float[] getAutoBrightnessBrighteningLevels() {
+        return mBrightnessLevels;
+    }
+
+    /**
      * @return Default peak refresh rate of the associated display
      */
     public int getDefaultPeakRefreshRate() {
@@ -1741,6 +1767,13 @@
         return mHostUsiVersion;
     }
 
+    /**
+     * @return Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+     */
+    public float getBrightnessCapForWearBedtimeMode() {
+        return mBrightnessCapForWearBedtimeMode;
+    }
+
     @Override
     public String toString() {
         return "DisplayDeviceConfig{"
@@ -1844,6 +1877,7 @@
                 + mAutoBrightnessDarkeningLightDebounceIdle
                 + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
                 + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+                + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "\n"
@@ -1867,36 +1901,39 @@
                 + ", mHighAmbientBrightnessThresholds= "
                 + Arrays.toString(mHighAmbientBrightnessThresholds)
                 + "\n"
-                + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
+                + "mScreenOffBrightnessSensorValueToLux= " + Arrays.toString(
                 mScreenOffBrightnessSensorValueToLux)
                 + "\n"
                 + "mUsiVersion= " + mHostUsiVersion + "\n"
-                + "mHdrBrightnessData" + mHdrBrightnessData
+                + "mHdrBrightnessData= " + mHdrBrightnessData + "\n"
+                + "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode
                 + "}";
     }
 
     private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
-            String suffixFormat, long idNumber) {
+            String suffixFormat, long idNumber, DisplayManagerFlags flags) {
 
         final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
         final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
         final File filePath = Environment.buildPath(
                 baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
-        final DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+        final DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         if (config.initFromFile(filePath)) {
             return config;
         }
         return null;
     }
 
-    private static DisplayDeviceConfig getConfigFromGlobalXml(Context context) {
-        DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+    private static DisplayDeviceConfig getConfigFromGlobalXml(Context context,
+            DisplayManagerFlags flags) {
+        DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         config.initFromGlobalXml();
         return config;
     }
 
-    private static DisplayDeviceConfig getConfigFromPmValues(Context context) {
-        DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+    private static DisplayDeviceConfig getConfigFromPmValues(Context context,
+            DisplayManagerFlags flags) {
+        DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         config.initFromDefaultValues();
         return config;
     }
@@ -1938,6 +1975,7 @@
                 loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
                 loadUsiVersion(config);
                 mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
+                loadBrightnessCapForWearBedtimeMode(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -1961,6 +1999,7 @@
         loadAutoBrightnessConfigsFromConfigXml();
         loadAutoBrightnessAvailableFromConfigXml();
         loadRefreshRateSetting(null);
+        loadBrightnessCapForWearBedtimeModeFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -2599,8 +2638,23 @@
      * loading the value from the display config, and if not present, falls back to config.xml.
      */
     private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
-        if (autoBrightnessConfig == null
-                || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
+        if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
+                && autoBrightnessConfig.getLuxToBrightnessMapping() != null) {
+            LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping();
+            final int size = mapping.getMap().getPoint().size();
+            mBrightnessLevels = new float[size];
+            mBrightnessLevelsLux = new float[size];
+            for (int i = 0; i < size; i++) {
+                float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
+                mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight);
+                mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
+                        .floatValue();
+            }
+            if (size > 0 && mBrightnessLevelsLux[0] != 0) {
+                throw new IllegalArgumentException(
+                        "The first lux value in the display brightness mapping must be 0");
+            }
+        } else {
             mBrightnessLevelsNits = getFloatArray(mContext.getResources()
                     .obtainTypedArray(com.android.internal.R.array
                             .config_autoBrightnessDisplayValuesNits), PowerManager
@@ -2608,18 +2662,6 @@
             mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
                     .getIntArray(com.android.internal.R.array
                             .config_autoBrightnessLevels));
-        } else {
-            final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
-                    .getDisplayBrightnessPoint().size();
-            mBrightnessLevelsNits = new float[size];
-            // The first control point is implicit and always at 0 lux.
-            mBrightnessLevelsLux = new float[size + 1];
-            for (int i = 0; i < size; i++) {
-                mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getNits().floatValue();
-                mBrightnessLevelsLux[i + 1] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getLux().floatValue();
-            }
         }
     }
 
@@ -3350,6 +3392,23 @@
                 : null;
     }
 
+    private void loadBrightnessCapForWearBedtimeMode(DisplayConfiguration config) {
+        if (config != null) {
+            BigDecimal configBrightnessCap = config.getScreenBrightnessCapForWearBedtimeMode();
+            if (configBrightnessCap != null) {
+                mBrightnessCapForWearBedtimeMode = configBrightnessCap.floatValue();
+            } else {
+                loadBrightnessCapForWearBedtimeModeFromConfigXml();
+            }
+        }
+    }
+
+    private void loadBrightnessCapForWearBedtimeModeFromConfigXml() {
+        mBrightnessCapForWearBedtimeMode = BrightnessSynchronizer.brightnessIntToFloat(
+                mContext.getResources().getInteger(com.android.internal.R.integer
+                        .config_screenBrightnessCapForWearBedtimeMode));
+    }
+
     /**
      * Container for high brightness mode configuration data.
      */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 11f4e5f..2ab15e6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -576,7 +576,8 @@
                 foldSettingProvider,
                 mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags);
-        mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
+        mBrightnessSynchronizer = new BrightnessSynchronizer(mContext,
+                mFlags.isBrightnessIntRangeUserPerceptionEnabled());
         Resources resources = mContext.getResources();
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
@@ -1852,7 +1853,7 @@
             // early apps like SetupWizard/Launcher. In particular, SUW is displayed using
             // the virtual display inside VR before any VR-specific apps even run.
             mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,
-                    mHandler, mDisplayDeviceRepo);
+                    mHandler, mDisplayDeviceRepo, mFlags);
             if (mVirtualDisplayAdapter != null) {
                 registerDisplayAdapterLocked(mVirtualDisplayAdapter);
             }
@@ -1870,7 +1871,7 @@
 
     private void registerOverlayDisplayAdapterLocked() {
         registerDisplayAdapterLocked(new OverlayDisplayAdapter(
-                mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));
+                mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler, mFlags));
     }
 
     private void registerWifiDisplayAdapterLocked() {
@@ -1879,7 +1880,7 @@
                 || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
             mWifiDisplayAdapter = new WifiDisplayAdapter(
                     mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,
-                    mPersistentDataStore);
+                    mPersistentDataStore, mFlags);
             registerDisplayAdapterLocked(mWifiDisplayAdapter);
         }
     }
@@ -3287,8 +3288,10 @@
     @VisibleForTesting
     static class Injector {
         VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener);
+                Handler handler, DisplayAdapter.Listener displayAdapterListener,
+                DisplayManagerFlags flags) {
+            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+                    flags);
         }
 
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
@@ -4948,20 +4951,8 @@
                     return null;
                 }
 
-                DisplayOffloadSession session =
-                        new DisplayOffloadSession() {
-                            @Override
-                            public void setDozeStateOverride(int displayState) {
-                                synchronized (mSyncRoot) {
-                                    displayPowerController.overrideDozeScreenState(displayState);
-                                }
-                            }
-
-                            @Override
-                            public DisplayOffloader getDisplayOffloader() {
-                                return displayOffloader;
-                            }
-                        };
+                DisplayOffloadSessionImpl session = new DisplayOffloadSessionImpl(displayOffloader,
+                        displayPowerController);
                 logicalDisplay.setDisplayOffloadSessionLocked(session);
                 displayPowerController.setDisplayOffloadSession(session);
                 return session;
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
new file mode 100644
index 0000000..1bd556b
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.Trace;
+
+/**
+ * An implementation of the offload session that keeps track of whether the session is active.
+ * An offload session is used to control the display's brightness using the offload chip.
+ */
+public class DisplayOffloadSessionImpl implements DisplayManagerInternal.DisplayOffloadSession {
+
+    @Nullable
+    private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+    private final DisplayPowerControllerInterface mDisplayPowerController;
+    private boolean mIsActive;
+
+    public DisplayOffloadSessionImpl(
+            @Nullable DisplayManagerInternal.DisplayOffloader displayOffloader,
+            DisplayPowerControllerInterface displayPowerController) {
+        mDisplayOffloader = displayOffloader;
+        mDisplayPowerController = displayPowerController;
+    }
+
+    @Override
+    public void setDozeStateOverride(int displayState) {
+        mDisplayPowerController.overrideDozeScreenState(displayState);
+    }
+
+    @Override
+    public boolean isActive() {
+        return mIsActive;
+    }
+
+    @Override
+    public void updateBrightness(float brightness) {
+        if (mIsActive) {
+            mDisplayPowerController.setBrightnessFromOffload(brightness);
+        }
+    }
+
+    /**
+     * Start the offload session. The method returns if the session is already active.
+     * @return Whether the session was started successfully
+     */
+    public boolean startOffload() {
+        if (mDisplayOffloader == null || mIsActive) {
+            return false;
+        }
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload");
+        try {
+            return mIsActive = mDisplayOffloader.startOffload();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
+        }
+    }
+
+    /**
+     * Stop the offload session. The method returns if the session is not active.
+     */
+    public void stopOffload() {
+        if (mDisplayOffloader == null || !mIsActive) {
+            return;
+        }
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#stopOffload");
+        try {
+            mDisplayOffloader.stopOffload();
+            mIsActive = false;
+            mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5761c31..f09fcea 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -20,6 +20,7 @@
 import android.animation.ObjectAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -2224,6 +2225,11 @@
     }
 
     @Override
+    public void setBrightnessFromOffload(float brightness) {
+        // The old DPC is no longer supported
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -3247,12 +3253,15 @@
         }
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private void noteScreenBrightness(float brightness) {
         if (mBatteryStats != null) {
             try {
                 // TODO(brightnessfloat): change BatteryStats to use float
-                mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
-                        brightness));
+                int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled()
+                        ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness)
+                        : BrightnessSynchronizer.brightnessFloatToInt(brightness);
+                mBatteryStats.noteScreenBrightness(brightnessInt);
             } catch (RemoteException e) {
                 // same process
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a6155da..5310e43 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 android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -151,6 +152,7 @@
     private static final int MSG_SET_DWBC_STRONG_MODE = 14;
     private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
     private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
+    private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
 
 
 
@@ -562,7 +564,7 @@
                 new DisplayBrightnessController(context, null,
                         mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
-                        new HandlerExecutor(mHandler));
+                        new HandlerExecutor(mHandler), flags);
 
         mBrightnessClamperController = mInjector.getBrightnessClamperController(
                 mHandler, modeChangeCallback::run,
@@ -1394,7 +1396,8 @@
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
-        boolean updateScreenBrightnessSetting = false;
+        boolean updateScreenBrightnessSetting =
+                displayBrightnessState.shouldUpdateScreenBrightnessSetting();
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
         int brightnessAdjustmentFlags = 0;
@@ -1854,6 +1857,13 @@
     }
 
     @Override
+    public void setBrightnessFromOffload(float brightness) {
+        Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
+                Float.floatToIntBits(brightness), 0 /*unused*/);
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -2627,12 +2637,15 @@
         }
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private void noteScreenBrightness(float brightness) {
         if (mBatteryStats != null) {
             try {
                 // TODO(brightnessfloat): change BatteryStats to use float
-                mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
-                        brightness));
+                int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled()
+                        ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness)
+                        : BrightnessSynchronizer.brightnessFloatToInt(brightness);
+                mBatteryStats.noteScreenBrightness(brightnessInt);
             } catch (RemoteException e) {
                 // same process
             }
@@ -2886,6 +2899,11 @@
                 case MSG_SET_DWBC_LOGGING_ENABLED:
                     setDwbcLoggingEnabled(msg.arg1);
                     break;
+                case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
+                    mDisplayBrightnessController.setBrightnessFromOffload(
+                            Float.intBitsToFloat(msg.arg1));
+                    updatePowerState();
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 181386a..72079a4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -22,6 +22,7 @@
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
 
 import java.io.PrintWriter;
 
@@ -150,6 +151,14 @@
     void setTemporaryAutoBrightnessAdjustment(float adjustment);
 
     /**
+     * Sets temporary brightness from the offload chip until we get a brightness value from
+     * the light sensor.
+     * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
+     * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored.
+     */
+    void setBrightnessFromOffload(float brightness);
+
+    /**
      * Gets the screen brightness setting
      */
     float getScreenBrightnessSetting();
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index be3207d..22898a6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -19,11 +19,11 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
+import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.hardware.sidekick.SidekickInternal;
 import android.os.Build;
 import android.os.Handler;
@@ -84,8 +84,6 @@
 
     private final boolean mIsBootDisplayModeSupported;
 
-    private final DisplayManagerFlags mFlags;
-
     private final DisplayNotificationManager mDisplayNotificationManager;
 
     private Context mOverlayContext;
@@ -103,12 +101,11 @@
             Listener listener, DisplayManagerFlags flags,
             DisplayNotificationManager displayNotificationManager,
             Injector injector) {
-        super(syncRoot, context, handler, listener, TAG);
+        super(syncRoot, context, handler, listener, TAG, flags);
         mDisplayNotificationManager = displayNotificationManager;
         mInjector = injector;
         mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
         mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
-        mFlags = flags;
     }
 
     @Override
@@ -238,7 +235,6 @@
         private boolean mAllmRequested;
         private boolean mGameContentTypeRequested;
         private boolean mSidekickActive;
-        private boolean mDisplayOffloadActive;
         private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
         // The supported display modes according to SurfaceFlinger
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
@@ -511,7 +507,7 @@
             // Load display device config
             final Context context = getOverlayContext();
             mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
-                    mIsFirstDisplay);
+                    mIsFirstDisplay, getFeatureFlags());
 
             // Load brightness HWC quirk
             mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
@@ -765,7 +761,7 @@
                 final int state,
                 final float brightnessState,
                 final float sdrBrightnessState,
-                DisplayOffloadSession displayOffloadSession) {
+                @Nullable DisplayOffloadSessionImpl displayOffloadSession) {
 
             // Assume that the brightness is off if the display is being turned off.
             assert state != Display.STATE_OFF
@@ -832,25 +828,14 @@
                                     + ", state=" + Display.stateToString(state) + ")");
                         }
 
-                        DisplayOffloader displayOffloader =
-                                displayOffloadSession == null
-                                        ? null
-                                        : displayOffloadSession.getDisplayOffloader();
-
-                        boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled();
+                        boolean isDisplayOffloadEnabled =
+                                getFeatureFlags().isDisplayOffloadEnabled();
 
                         // We must tell sidekick/displayoffload to stop controlling the display
                         // before we can change its power mode, so do that first.
                         if (isDisplayOffloadEnabled) {
-                            if (mDisplayOffloadActive && displayOffloader != null) {
-                                Trace.traceBegin(Trace.TRACE_TAG_POWER,
-                                        "DisplayOffloader#stopOffload");
-                                try {
-                                    displayOffloader.stopOffload();
-                                } finally {
-                                    Trace.traceEnd(Trace.TRACE_TAG_POWER);
-                                }
-                                mDisplayOffloadActive = false;
+                            if (displayOffloadSession != null) {
+                                displayOffloadSession.stopOffload();
                             }
                         } else {
                             if (mSidekickActive) {
@@ -881,16 +866,9 @@
                         // have a sidekick/displayoffload available, tell it now that it can take
                         // control.
                         if (isDisplayOffloadEnabled) {
-                            if (DisplayOffloadSession.isSupportedOffloadState(state) &&
-                                    displayOffloader != null
-                                    && !mDisplayOffloadActive) {
-                                Trace.traceBegin(
-                                        Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload");
-                                try {
-                                    mDisplayOffloadActive = displayOffloader.startOffload();
-                                } finally {
-                                    Trace.traceEnd(Trace.TRACE_TAG_POWER);
-                                }
+                            if (DisplayOffloadSession.isSupportedOffloadState(state)
+                                    && displayOffloadSession != null) {
+                                displayOffloadSession.startOffload();
                             }
                         } else {
                             if (Display.isSuspendedState(state) && state != Display.STATE_OFF
@@ -1397,8 +1375,8 @@
         }
 
         public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
-                long physicalDisplayId, boolean isFirstDisplay) {
-            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+                long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
+            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 3d4209e..db636d6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -17,6 +17,8 @@
 package com.android.server.display;
 
 import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
+import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,6 +35,7 @@
 
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.wm.utils.DisplayInfoOverrides;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
@@ -138,7 +141,7 @@
     private final Rect mTempDisplayRect = new Rect();
 
     /** A session token that controls the offloading operations of this logical display. */
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+    private DisplayOffloadSessionImpl mDisplayOffloadSession;
 
     /**
      * Name of a display group to which the display is assigned.
@@ -252,24 +255,8 @@
     public DisplayInfo getDisplayInfoLocked() {
         if (mInfo.get() == null) {
             DisplayInfo info = new DisplayInfo();
-            info.copyFrom(mBaseDisplayInfo);
-            if (mOverrideDisplayInfo != null) {
-                info.appWidth = mOverrideDisplayInfo.appWidth;
-                info.appHeight = mOverrideDisplayInfo.appHeight;
-                info.smallestNominalAppWidth = mOverrideDisplayInfo.smallestNominalAppWidth;
-                info.smallestNominalAppHeight = mOverrideDisplayInfo.smallestNominalAppHeight;
-                info.largestNominalAppWidth = mOverrideDisplayInfo.largestNominalAppWidth;
-                info.largestNominalAppHeight = mOverrideDisplayInfo.largestNominalAppHeight;
-                info.logicalWidth = mOverrideDisplayInfo.logicalWidth;
-                info.logicalHeight = mOverrideDisplayInfo.logicalHeight;
-                info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
-                info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
-                info.rotation = mOverrideDisplayInfo.rotation;
-                info.displayCutout = mOverrideDisplayInfo.displayCutout;
-                info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
-                info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
-                info.displayShape = mOverrideDisplayInfo.displayShape;
-            }
+            copyDisplayInfoFields(info, mBaseDisplayInfo, mOverrideDisplayInfo,
+                    WM_OVERRIDE_FIELDS);
             mInfo.set(info);
         }
         return mInfo.get();
@@ -969,12 +956,11 @@
         return mDisplayGroupName;
     }
 
-    public void setDisplayOffloadSessionLocked(
-            DisplayManagerInternal.DisplayOffloadSession session) {
+    public void setDisplayOffloadSessionLocked(DisplayOffloadSessionImpl session) {
         mDisplayOffloadSession = session;
     }
 
-    public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() {
+    public DisplayOffloadSessionImpl getDisplayOffloadSessionLocked() {
         return mDisplayOffloadSession;
     }
 
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 2ce7690..22ff2d0 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
 
 import java.io.PrintWriter;
@@ -134,8 +135,9 @@
 
     // Called with SyncRoot lock held.
     public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener, Handler uiHandler) {
-        super(syncRoot, context, handler, listener, TAG);
+            Context context, Handler handler, Listener listener, Handler uiHandler,
+            DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
         mUiHandler = uiHandler;
     }
 
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 5ba042c..e38c2c5 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,6 +20,8 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
+import com.android.internal.display.BrightnessUtils;
+
 /**
  * A custom animator that progressively updates a property value at
  * a given variable rate until it reaches a particular target value.
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 90e32a6..ec5ad7d 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,7 +43,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Point;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
@@ -62,6 +61,7 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -89,7 +89,7 @@
 
     // Called with SyncRoot lock held.
     public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener) {
+            Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
         this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
             @Override
             public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
@@ -100,14 +100,15 @@
             public void destroyDisplay(IBinder displayToken) {
                 DisplayControl.destroyDisplay(displayToken);
             }
-        });
+        }, featureFlags);
     }
 
     @VisibleForTesting
     VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener,
-            SurfaceControlDisplayFactory surfaceControlDisplayFactory) {
-        super(syncRoot, context, handler, listener, TAG);
+            SurfaceControlDisplayFactory surfaceControlDisplayFactory,
+            DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
         mHandler = handler;
         mSurfaceControlDisplayFactory = surfaceControlDisplayFactory;
     }
@@ -380,7 +381,7 @@
 
         @Override
         public Runnable requestDisplayStateLocked(int state, float brightnessState,
-                float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
+                float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
             if (state != mDisplayState) {
                 mDisplayState = state;
                 if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 7660cf8..aa98cd8 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
@@ -99,8 +100,8 @@
     // Called with SyncRoot lock held.
     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener,
-            PersistentDataStore persistentDataStore) {
-        super(syncRoot, context, handler, listener, TAG);
+            PersistentDataStore persistentDataStore, DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
 
         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
             throw new RuntimeException("WiFi display was requested, "
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index d7ae269..8fe5f21 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -39,7 +39,8 @@
     public static final int REASON_BOOST = 8;
     public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9;
     public static final int REASON_FOLLOWER = 10;
-    public static final int REASON_MAX = REASON_FOLLOWER;
+    public static final int REASON_OFFLOAD = 11;
+    public static final int REASON_MAX = REASON_OFFLOAD;
 
     public static final int MODIFIER_DIMMED = 0x1;
     public static final int MODIFIER_LOW_POWER = 0x2;
@@ -196,6 +197,8 @@
                 return "screen_off_brightness_sensor";
             case REASON_FOLLOWER:
                 return "follower";
+            case REASON_OFFLOAD:
+                return "offload";
             default:
                 return Integer.toString(reason);
         }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index d6f0098..617befb 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -31,6 +31,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 
@@ -104,7 +105,8 @@
      */
     public DisplayBrightnessController(Context context, Injector injector, int displayId,
             float defaultScreenBrightness, BrightnessSetting brightnessSetting,
-            Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor) {
+            Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor,
+            DisplayManagerFlags flags) {
         if (injector == null) {
             injector = new Injector();
         }
@@ -116,7 +118,7 @@
         mCurrentScreenBrightness = getScreenBrightnessSetting();
         mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
         mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
-                displayId);
+                displayId, flags);
         mBrightnessChangeExecutor = brightnessChangeExecutor;
         mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
@@ -172,6 +174,18 @@
     }
 
     /**
+     * Sets the brightness from the offload session.
+     */
+    public void setBrightnessFromOffload(float brightness) {
+        synchronized (mLock) {
+            if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+                mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
+                        .setOffloadScreenBrightness(brightness);
+            }
+        }
+    }
+
+    /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
      */
@@ -423,8 +437,9 @@
     @VisibleForTesting
     static class Injector {
         DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
-                int displayId) {
-            return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId);
+                int displayId, DisplayManagerFlags flags) {
+            return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId,
+                    flags);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index f141c20..055f94a 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -17,6 +17,7 @@
 package com.android.server.display.brightness;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
 import android.util.IndentingPrintWriter;
@@ -31,9 +32,11 @@
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
 import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
 import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 
@@ -63,6 +66,10 @@
     private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
     // Controls brightness when automatic (adaptive) brightness is running.
     private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+    // Controls the brightness if adaptive brightness is on and there exists an active offload
+    // session. Brightness value is provided by the offload session.
+    @Nullable
+    private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
 
     // We take note of the old brightness strategy so that we can know when the strategy changes.
     private String mOldBrightnessStrategyName;
@@ -72,7 +79,8 @@
     /**
      * The constructor of DozeBrightnessStrategy.
      */
-    public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) {
+    public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId,
+            DisplayManagerFlags flags) {
         if (injector == null) {
             injector = new Injector();
         }
@@ -85,6 +93,11 @@
         mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
         mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
+        if (flags.isDisplayOffloadEnabled()) {
+            mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy();
+        } else {
+            mOffloadBrightnessStrategy = null;
+        }
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -114,6 +127,9 @@
         } else if (BrightnessUtils.isValidBrightnessValue(
                 mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
             displayBrightnessStrategy = mTemporaryBrightnessStrategy;
+        } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+                mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
+            displayBrightnessStrategy = mOffloadBrightnessStrategy;
         }
 
         if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
@@ -138,6 +154,11 @@
         return mAutomaticBrightnessStrategy;
     }
 
+    @Nullable
+    public OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+        return mOffloadBrightnessStrategy;
+    }
+
     /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
@@ -159,6 +180,9 @@
                         + mAllowAutoBrightnessWhileDozingConfig);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
         mTemporaryBrightnessStrategy.dump(ipw);
+        if (mOffloadBrightnessStrategy != null) {
+            mOffloadBrightnessStrategy.dump(ipw);
+        }
     }
 
     /**
@@ -210,5 +234,9 @@
         AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
             return new AutomaticBrightnessStrategy(context, displayId);
         }
+
+        OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+            return new OffloadBrightnessStrategy();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index dfcda40..8a884b6 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -17,6 +17,7 @@
 package com.android.server.display.brightness.clamper;
 
 import android.annotation.NonNull;
+import android.os.Handler;
 import android.os.PowerManager;
 
 import com.android.server.display.DisplayBrightnessState;
@@ -31,6 +32,18 @@
     protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
     protected boolean mIsActive = false;
 
+    @NonNull
+    protected final Handler mHandler;
+
+    @NonNull
+    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
+
+    BrightnessClamper(Handler handler,
+            BrightnessClamperController.ClamperChangeListener changeListener) {
+        mHandler = handler;
+        mChangeListener = changeListener;
+    }
+
     float getBrightnessCap() {
         return mBrightnessCap;
     }
@@ -60,6 +73,7 @@
 
     protected enum Type {
         THERMAL,
-        POWER
+        POWER,
+        BEDTIME_MODE
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index b574919..765608e 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -90,7 +90,8 @@
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
+        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
+                context);
         mModifiers = injector.getModifiers(context);
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -234,7 +235,7 @@
 
         List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
                 ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, Context context) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
             clampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListener, data));
@@ -242,6 +243,10 @@
                 clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
                             data));
             }
+            if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
+                clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
+                        clamperChangeListener, data));
+            }
             return clampers;
         }
 
@@ -257,7 +262,8 @@
      * Config Data for clampers
      */
     public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
-                BrightnessPowerClamper.PowerData {
+                BrightnessPowerClamper.PowerData,
+            BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
         @NonNull
         private final String mUniqueDisplayId;
         @NonNull
@@ -315,5 +321,10 @@
         public PowerThrottlingConfigData getPowerThrottlingConfigData() {
             return mDisplayDeviceConfig.getPowerThrottlingConfigData();
         }
+
+        @Override
+        public float getBrightnessWearBedtimeModeCap() {
+            return mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
index 339b589..790322d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -49,10 +49,6 @@
     private final Injector mInjector;
     @NonNull
     private final DeviceConfigParameterProvider mConfigParameterProvider;
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final ClamperChangeListener mChangeListener;
     @Nullable
     private PmicMonitor mPmicMonitor;
     // data from DeviceConfig, for all displays, for all dataSets
@@ -99,10 +95,9 @@
     @VisibleForTesting
     BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
             PowerData powerData) {
+        super(handler, listener);
         mInjector = injector;
         mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
-        mHandler = handler;
-        mChangeListener = listener;
 
         mHandler.post(() -> {
             setDisplayData(powerData);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
index 8ae962b..944a8a6 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -54,10 +54,6 @@
     private final IThermalService mThermalService;
     @NonNull
     private final DeviceConfigParameterProvider mConfigParameterProvider;
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final ClamperChangeListener mChangelistener;
     // data from DeviceConfig, for all displays, for all dataSets
     // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
     @NonNull
@@ -108,10 +104,9 @@
     @VisibleForTesting
     BrightnessThermalClamper(Injector injector, Handler handler,
             ClamperChangeListener listener, ThermalData thermalData) {
+        super(handler, listener);
         mThermalService = injector.getThermalService();
         mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
-        mHandler = handler;
-        mChangelistener = listener;
         mHandler.post(() -> {
             setDisplayData(thermalData);
             loadOverrideData();
@@ -220,7 +215,7 @@
         if (brightnessCap  != mBrightnessCap || mIsActive != isActive) {
             mBrightnessCap = brightnessCap;
             mIsActive = isActive;
-            mChangelistener.onChanged();
+            mChangeListener.onChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
new file mode 100644
index 0000000..7e853bf
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+public class BrightnessWearBedtimeModeClamper extends
+        BrightnessClamper<BrightnessWearBedtimeModeClamper.WearBedtimeModeData> {
+
+    public static final int BEDTIME_MODE_OFF = 0;
+    public static final int BEDTIME_MODE_ON = 1;
+
+    private final Context mContext;
+
+    private final ContentObserver mSettingsObserver;
+
+    BrightnessWearBedtimeModeClamper(Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        this(new Injector(), handler, context, listener, data);
+    }
+
+    @VisibleForTesting
+    BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        super(handler, listener);
+        mContext = context;
+        mBrightnessCap = data.getBrightnessWearBedtimeModeCap();
+        mSettingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                final int bedtimeModeSetting = Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.Wearable.BEDTIME_MODE,
+                        BEDTIME_MODE_OFF);
+                mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON;
+                mChangeListener.onChanged();
+            }
+        };
+        injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver);
+    }
+
+    @NonNull
+    @Override
+    Type getType() {
+        return Type.BEDTIME_MODE;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {}
+
+    @Override
+    void onDisplayChanged(WearBedtimeModeData displayData) {
+        mHandler.post(() -> {
+            mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap();
+            mChangeListener.onChanged();
+        });
+    }
+
+    @Override
+    void stop() {
+        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+    }
+
+    interface WearBedtimeModeData {
+        float getBrightnessWearBedtimeModeCap();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE),
+                    /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index bcd5259..3c23b5c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -107,6 +107,7 @@
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
                 && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && brightnessReason != BrightnessReason.REASON_OVERRIDE
+                && brightnessReason != BrightnessReason.REASON_OFFLOAD
                 && mAutomaticBrightnessController != null;
         mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
new file mode 100644
index 0000000..55f8914
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the display when auto-brightness is on, the screen has just turned on
+ * and there is no available lux reading yet. The brightness value is read from the offload chip.
+ */
+public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy {
+
+    private float mOffloadScreenBrightness;
+
+    public OffloadBrightnessStrategy() {
+        mOffloadScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    }
+
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(mOffloadScreenBrightness)
+                .setSdrBrightness(mOffloadScreenBrightness)
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(getName())
+                .setIsSlowChange(false)
+                .setShouldUpdateScreenBrightnessSetting(true)
+                .build();
+    }
+
+    @Override
+    public String getName() {
+        return "OffloadBrightnessStrategy";
+    }
+
+    public float getOffloadScreenBrightness() {
+        return mOffloadScreenBrightness;
+    }
+
+    public void setOffloadScreenBrightness(float offloadScreenBrightness) {
+        mOffloadScreenBrightness = offloadScreenBrightness;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("OffloadBrightnessStrategy:");
+        writer.println("  mOffloadScreenBrightness:" + mOffloadScreenBrightness);
+    }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index b1c0762..c71f0cf2 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -82,6 +82,22 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
             com.android.graphics.surfaceflinger.flags.Flags::enableSmallAreaDetection);
 
+    private final FlagState mBrightnessIntRangeUserPerceptionFlagState = new FlagState(
+            Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION,
+            Flags::brightnessIntRangeUserPerception);
+
+    private final FlagState mVsyncProximityVote = new FlagState(
+            Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE,
+            Flags::enableExternalVsyncProximityVote);
+
+    private final FlagState mBrightnessWearBedtimeModeClamperFlagState = new FlagState(
+            Flags.FLAG_BRIGHTNESS_WEAR_BEDTIME_MODE_CLAMPER,
+            Flags::brightnessWearBedtimeModeClamper);
+
+    private final FlagState mAutoBrightnessModesFlagState = new FlagState(
+            Flags.FLAG_AUTO_BRIGHTNESS_MODES,
+            Flags::autoBrightnessModes);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -162,6 +178,25 @@
         return mSmallAreaDetectionFlagState.isEnabled();
     }
 
+    public boolean isBrightnessIntRangeUserPerceptionEnabled() {
+        return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
+    }
+
+    public boolean isExternalVsyncProximityVoteEnabled() {
+        return mVsyncProximityVote.isEnabled();
+    }
+
+    public boolean isBrightnessWearBedtimeModeClamperEnabled() {
+        return mBrightnessWearBedtimeModeClamperFlagState.isEnabled();
+    }
+
+    /**
+     * @return Whether generic auto-brightness modes are enabled
+     */
+    public boolean areAutoBrightnessModesEnabled() {
+        return mAutoBrightnessModesFlagState.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -179,6 +214,10 @@
         pw.println(" " + mNbmControllerFlagState);
         pw.println(" " + mPowerThrottlingClamperFlagState);
         pw.println(" " + mSmallAreaDetectionFlagState);
+        pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
+        pw.println(" " + mVsyncProximityVote);
+        pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
+        pw.println(" " + mAutoBrightnessModesFlagState);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9ab9c9d..9dfa1ee 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -104,3 +104,35 @@
     bug: "211737588"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "brightness_int_range_user_perception"
+    namespace: "display_manager"
+    description: "Feature flag for converting the brightness integer range to the user perception scale"
+    bug: "183655602"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_external_vsync_proximity_vote"
+    namespace: "display_manager"
+    description: "Feature flag for external vsync proximity vote"
+    bug: "284866750"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "brightness_wear_bedtime_mode_clamper"
+    namespace: "display_manager"
+    description: "Feature flag for the Wear Bedtime mode brightness clamper"
+    bug: "293613040"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "auto_brightness_modes"
+    namespace: "display_manager"
+    description: "Feature flag for generic auto-brightness modes"
+    bug: "293613040"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
new file mode 100644
index 0000000..c04df64
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
@@ -0,0 +1,59 @@
+/*
+ * 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.display.mode;
+
+import java.util.Objects;
+
+class BaseModeRefreshRateVote implements Vote {
+
+    /**
+     * The preferred refresh rate selected by the app. It is used to validate that the summary
+     * refresh rate ranges include this value, and are not restricted by a lower priority vote.
+     */
+    final float mAppRequestBaseModeRefreshRate;
+
+    BaseModeRefreshRateVote(float baseModeRefreshRate) {
+        mAppRequestBaseModeRefreshRate = baseModeRefreshRate;
+    }
+
+    @Override
+    public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+        if (summary.appRequestBaseModeRefreshRate == 0f
+                && mAppRequestBaseModeRefreshRate > 0f) {
+            summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof BaseModeRefreshRateVote that)) return false;
+        return Float.compare(that.mAppRequestBaseModeRefreshRate,
+                mAppRequestBaseModeRefreshRate) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAppRequestBaseModeRefreshRate);
+    }
+
+    @Override
+    public String toString() {
+        return "BaseModeRefreshRateVote{ mAppRequestBaseModeRefreshRate="
+                + mAppRequestBaseModeRefreshRate + " }";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java
new file mode 100644
index 0000000..f24fe3a
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/CombinedVote.java
@@ -0,0 +1,51 @@
+/*
+ * 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.display.mode;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+class CombinedVote implements Vote {
+    final List<Vote> mVotes;
+
+    CombinedVote(List<Vote> votes) {
+        mVotes = Collections.unmodifiableList(votes);
+    }
+
+    @Override
+    public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+        mVotes.forEach(vote -> vote.updateSummary(summary));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof CombinedVote that)) return false;
+        return Objects.equals(mVotes, that.mVotes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mVotes);
+    }
+
+    @Override
+    public String toString() {
+        return "CombinedVote{ mVotes=" + mVotes + " }";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
new file mode 100644
index 0000000..2fc5590
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
@@ -0,0 +1,56 @@
+/*
+ * 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.display.mode;
+
+import java.util.Objects;
+
+class DisableRefreshRateSwitchingVote implements Vote {
+
+    /**
+     * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
+     * a single value).
+     */
+    final boolean mDisableRefreshRateSwitching;
+
+    DisableRefreshRateSwitchingVote(boolean disableRefreshRateSwitching) {
+        mDisableRefreshRateSwitching = disableRefreshRateSwitching;
+    }
+
+    @Override
+    public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+        summary.disableRefreshRateSwitching =
+                summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DisableRefreshRateSwitchingVote that)) return false;
+        return mDisableRefreshRateSwitching == that.mDisableRefreshRateSwitching;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisableRefreshRateSwitching);
+    }
+
+    @Override
+    public String toString() {
+        return "DisableRefreshRateSwitchingVote{ mDisableRefreshRateSwitching="
+                + mDisableRefreshRateSwitching + " }";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index fb6c9e3..8fa838d 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -262,7 +262,7 @@
         mVotesStorage.setLoggingEnabled(loggingEnabled);
     }
 
-    private static final class VoteSummary {
+    static final class VoteSummary {
         public float minPhysicalRefreshRate;
         public float maxPhysicalRefreshRate;
         public float minRenderFrameRate;
@@ -274,7 +274,12 @@
         public boolean disableRefreshRateSwitching;
         public float appRequestBaseModeRefreshRate;
 
-        VoteSummary() {
+        public List<SupportedModesVote.SupportedMode> supportedModes;
+
+        final boolean mIsDisplayResolutionRangeVotingEnabled;
+
+        VoteSummary(boolean isDisplayResolutionRangeVotingEnabled) {
+            mIsDisplayResolutionRangeVotingEnabled = isDisplayResolutionRangeVotingEnabled;
             reset();
         }
 
@@ -322,46 +327,7 @@
                 continue;
             }
 
-            // For physical refresh rates, just use the tightest bounds of all the votes.
-            // The refresh rate cannot be lower than the minimal render frame rate.
-            final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min,
-                    vote.refreshRateRanges.render.min);
-            summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
-                    minPhysicalRefreshRate);
-            summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate,
-                    vote.refreshRateRanges.physical.max);
-
-            // Same goes to render frame rate, but frame rate cannot exceed the max physical
-            // refresh rate
-            final float maxRenderFrameRate = Math.min(vote.refreshRateRanges.render.max,
-                    vote.refreshRateRanges.physical.max);
-            summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate,
-                    vote.refreshRateRanges.render.min);
-            summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, maxRenderFrameRate);
-
-            // For display size, disable refresh rate switching and base mode refresh rate use only
-            // the first vote we come across (i.e. the highest priority vote that includes the
-            // attribute).
-            if (vote.height > 0 && vote.width > 0) {
-                if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) {
-                    summary.width = vote.width;
-                    summary.height = vote.height;
-                    summary.minWidth = vote.minWidth;
-                    summary.minHeight = vote.minHeight;
-                } else if (mIsDisplayResolutionRangeVotingEnabled) {
-                    summary.width = Math.min(summary.width, vote.width);
-                    summary.height = Math.min(summary.height, vote.height);
-                    summary.minWidth = Math.max(summary.minWidth, vote.minWidth);
-                    summary.minHeight = Math.max(summary.minHeight, vote.minHeight);
-                }
-            }
-            if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) {
-                summary.disableRefreshRateSwitching = true;
-            }
-            if (summary.appRequestBaseModeRefreshRate == 0f
-                    && vote.appRequestBaseModeRefreshRate > 0f) {
-                summary.appRequestBaseModeRefreshRate = vote.appRequestBaseModeRefreshRate;
-            }
+            vote.updateSummary(summary);
 
             if (mLoggingEnabled) {
                 Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority)
@@ -443,7 +409,7 @@
 
             ArrayList<Display.Mode> availableModes = new ArrayList<>();
             availableModes.add(defaultMode);
-            VoteSummary primarySummary = new VoteSummary();
+            VoteSummary primarySummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled);
             int lowestConsideredPriority = Vote.MIN_PRIORITY;
             int highestConsideredPriority = Vote.MAX_PRIORITY;
 
@@ -526,7 +492,7 @@
                                 + "]");
             }
 
-            VoteSummary appRequestSummary = new VoteSummary();
+            VoteSummary appRequestSummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled);
             summarizeVotes(
                     votes,
                     Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF,
diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
new file mode 100644
index 0000000..173b3c5
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
@@ -0,0 +1,120 @@
+/*
+ * 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.display.mode;
+
+import java.util.Objects;
+
+
+/**
+ * Information about the refresh rate frame rate ranges DM would like to set the display to.
+ */
+abstract class RefreshRateVote implements Vote {
+    final float mMinRefreshRate;
+
+    final float mMaxRefreshRate;
+
+    RefreshRateVote(float minRefreshRate, float maxRefreshRate) {
+        mMinRefreshRate = minRefreshRate;
+        mMaxRefreshRate = maxRefreshRate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RefreshRateVote that)) return false;
+        return Float.compare(that.mMinRefreshRate, mMinRefreshRate) == 0
+                && Float.compare(that.mMaxRefreshRate, mMaxRefreshRate) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMinRefreshRate, mMaxRefreshRate);
+    }
+
+    @Override
+    public String toString() {
+        return "RefreshRateVote{  mMinRefreshRate=" + mMinRefreshRate
+                + ", mMaxRefreshRate=" + mMaxRefreshRate + " }";
+    }
+
+    static class RenderVote extends RefreshRateVote {
+        RenderVote(float minRefreshRate, float maxRefreshRate) {
+            super(minRefreshRate, maxRefreshRate);
+        }
+
+        /**
+         * Summary:        minRender            minPhysical                    maxRender
+         *                    v                     v                             v
+         * -------------------|---------------------"-----------------------------|---------
+         *             ^                ^                   ^*             ^           ^
+         *  Vote: min(ignored)     min(applied)  min(applied+physical)  max(applied)  max(ignored)
+         */
+        @Override
+        public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+            summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate);
+            summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate);
+            // Physical refresh rate cannot be lower than the minimal render frame rate.
+            summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
+                    mMinRefreshRate);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof RefreshRateVote.RenderVote)) return false;
+            return super.equals(o);
+        }
+
+        @Override
+        public String toString() {
+            return "RenderVote{ " + super.toString() + " }";
+        }
+    }
+
+    static class PhysicalVote extends RefreshRateVote {
+        PhysicalVote(float minRefreshRate, float maxRefreshRate) {
+            super(minRefreshRate, maxRefreshRate);
+        }
+
+        /**
+         * Summary:        minPhysical                   maxRender               maxPhysical
+         *                    v                             v                        v
+         * -------------------"-----------------------------|----------------------"----------
+         *             ^             ^             ^*                 ^                   ^
+         *  Vote: min(ignored) min(applied)  max(applied+render)     max(applied)  max(ignored)
+         */
+        @Override
+        public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+            summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
+                    mMinRefreshRate);
+            summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate,
+                    mMaxRefreshRate);
+            // Render frame rate cannot exceed the max physical refresh rate
+            summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof RefreshRateVote.PhysicalVote)) return false;
+            return super.equals(o);
+        }
+
+        @Override
+        public String toString() {
+            return "PhysicalVote{ " + super.toString() + " }";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java
new file mode 100644
index 0000000..a9b18a5
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SizeVote.java
@@ -0,0 +1,88 @@
+/*
+ * 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.display.mode;
+
+import java.util.Objects;
+
+class SizeVote implements Vote {
+
+    /**
+     * The requested width of the display in pixels;
+     */
+    final int mWidth;
+
+    /**
+     * The requested height of the display in pixels;
+     */
+    final int mHeight;
+
+    /**
+     * Min requested width of the display in pixels;
+     */
+    final int mMinWidth;
+
+    /**
+     * Min requested height of the display in pixels;
+     */
+    final int mMinHeight;
+
+    SizeVote(int width, int height, int minWidth, int minHeight) {
+        mWidth = width;
+        mHeight = height;
+        mMinWidth = minWidth;
+        mMinHeight = minHeight;
+    }
+
+    @Override
+    public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+        if (mHeight > 0 && mWidth > 0) {
+            // For display size, disable refresh rate switching and base mode refresh rate use
+            // only the first vote we come across (i.e. the highest priority vote that includes
+            // the attribute).
+            if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) {
+                summary.width = mWidth;
+                summary.height = mHeight;
+                summary.minWidth = mMinWidth;
+                summary.minHeight = mMinHeight;
+            } else if (summary.mIsDisplayResolutionRangeVotingEnabled) {
+                summary.width = Math.min(summary.width, mWidth);
+                summary.height = Math.min(summary.height, mHeight);
+                summary.minWidth = Math.max(summary.minWidth, mMinWidth);
+                summary.minHeight = Math.max(summary.minHeight, mMinHeight);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SizeVote sizeVote)) return false;
+        return mWidth == sizeVote.mWidth && mHeight == sizeVote.mHeight
+                && mMinWidth == sizeVote.mMinWidth && mMinHeight == sizeVote.mMinHeight;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mWidth, mHeight, mMinWidth, mMinHeight);
+    }
+
+    @Override
+    public String toString() {
+        return "SizeVote{ mWidth=" + mWidth + ", mHeight=" + mHeight
+                + ", mMinWidth=" + mMinWidth + ", mMinHeight=" + mMinHeight + " }";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
new file mode 100644
index 0000000..b31461f
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
@@ -0,0 +1,92 @@
+/*
+ * 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.display.mode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+class SupportedModesVote implements Vote {
+
+    final List<SupportedMode> mSupportedModes;
+
+    SupportedModesVote(List<SupportedMode> supportedModes) {
+        mSupportedModes = Collections.unmodifiableList(supportedModes);
+    }
+
+    /**
+     * Summary should have subset of supported modes.
+     * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B)
+     * If summary.supportedModes==null then there is no restriction on supportedModes
+     */
+    @Override
+    public void updateSummary(DisplayModeDirector.VoteSummary summary) {
+        if (summary.supportedModes == null) {
+            summary.supportedModes = new ArrayList<>(mSupportedModes);
+        } else {
+            summary.supportedModes.retainAll(mSupportedModes);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SupportedModesVote that)) return false;
+        return mSupportedModes.equals(that.mSupportedModes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSupportedModes);
+    }
+
+    @Override
+    public String toString() {
+        return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }";
+    }
+
+    static class SupportedMode {
+        final float mPeakRefreshRate;
+        final float mVsyncRate;
+
+
+        SupportedMode(float peakRefreshRate, float vsyncRate) {
+            mPeakRefreshRate = peakRefreshRate;
+            mVsyncRate = vsyncRate;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SupportedMode that)) return false;
+            return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0
+                    && Float.compare(that.mVsyncRate, mVsyncRate) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPeakRefreshRate, mVsyncRate);
+        }
+
+        @Override
+        public String toString() {
+            return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate
+                    + ", mVsyncRate=" + mVsyncRate + " }";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index b6a6069..c1cdd69 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -16,15 +16,13 @@
 
 package com.android.server.display.mode;
 
-import android.view.SurfaceControl;
+import java.util.List;
 
-import java.util.Objects;
-
-final class Vote {
+interface Vote {
     // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
     // priority vote, it's overridden by all other considerations. It acts to set a default
     // frame rate for a device.
-    static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
+    int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
 
     // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
     // null. It is used to set a preferred refresh rate value in case the higher priority votes
@@ -32,21 +30,21 @@
     static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
 
     // High-brightness-mode may need a specific range of refresh-rates to function properly.
-    static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
+    int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
 
     // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
     // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
-    static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
+    int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
 
     // User setting preferred display resolution.
-    static final int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
+    int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
 
     // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
     // frame rate in certain cases, mostly to preserve power.
     // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
     // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
     // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
-    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5;
+    int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5;
 
     // We split the app request into different priorities in case we can satisfy one desire
     // without the other.
@@ -72,181 +70,100 @@
     // The preferred refresh rate is set on the main surface of the app outside of
     // DisplayModeDirector.
     // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
-    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6;
+    int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6;
 
-    static final int PRIORITY_APP_REQUEST_SIZE = 7;
+    int PRIORITY_APP_REQUEST_SIZE = 7;
 
     // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
     // rest of low priority voters. It votes [0, max(PEAK, MIN)]
-    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8;
+    int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8;
 
     // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz].
-    static final int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9;
+    int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9;
 
     // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT]
-    static final int PRIORITY_LIMIT_MODE = 10;
+    int PRIORITY_LIMIT_MODE = 10;
 
     // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
     // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11;
+    int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12;
+    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12;
 
     // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    static final int PRIORITY_LOW_POWER_MODE = 13;
+    int PRIORITY_LOW_POWER_MODE = 13;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14;
+    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    static final int PRIORITY_SKIN_TEMPERATURE = 15;
+    int PRIORITY_SKIN_TEMPERATURE = 15;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    static final int PRIORITY_PROXIMITY = 16;
+    int PRIORITY_PROXIMITY = 16;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    static final int PRIORITY_UDFPS = 17;
+    int PRIORITY_UDFPS = 17;
 
     // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
     // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
 
-    static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
-    static final int MAX_PRIORITY = PRIORITY_UDFPS;
+    int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
+    int MAX_PRIORITY = PRIORITY_UDFPS;
 
     // The cutoff for the app request refresh rate range. Votes with priorities lower than this
     // value will not be considered when constructing the app request refresh rate range.
-    static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
+    int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
             PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
 
     /**
      * A value signifying an invalid width or height in a vote.
      */
-    static final int INVALID_SIZE = -1;
+    int INVALID_SIZE = -1;
 
-    /**
-     * The requested width of the display in pixels, or INVALID_SIZE;
-     */
-    public final int width;
-    /**
-     * The requested height of the display in pixels, or INVALID_SIZE;
-     */
-    public final int height;
-    /**
-     * Min requested width of the display in pixels, or 0;
-     */
-    public final int minWidth;
-    /**
-     * Min requested height of the display in pixels, or 0;
-     */
-    public final int minHeight;
-    /**
-     * Information about the refresh rate frame rate ranges DM would like to set the display to.
-     */
-    public final SurfaceControl.RefreshRateRanges refreshRateRanges;
-
-    /**
-     * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
-     * a single value).
-     */
-    public final boolean disableRefreshRateSwitching;
-
-    /**
-     * The preferred refresh rate selected by the app. It is used to validate that the summary
-     * refresh rate ranges include this value, and are not restricted by a lower priority vote.
-     */
-    public final float appRequestBaseModeRefreshRate;
+    void updateSummary(DisplayModeDirector.VoteSummary summary);
 
     static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
-        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
-                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
-                /* minPhysicalRefreshRate= */ minRefreshRate,
-                /* maxPhysicalRefreshRate= */ maxRefreshRate,
-                /* minRenderFrameRate= */ 0,
-                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
-                /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate,
-                /* baseModeRefreshRate= */ 0f);
+        return new CombinedVote(
+                List.of(
+                        new RefreshRateVote.PhysicalVote(minRefreshRate, maxRefreshRate),
+                        new DisableRefreshRateSwitchingVote(minRefreshRate == maxRefreshRate)
+                )
+        );
     }
 
     static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
-        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
-                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
-                /* minPhysicalRefreshRate= */ 0,
-                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
-                minFrameRate,
-                maxFrameRate,
-                /* disableRefreshRateSwitching= */ false,
-                /* baseModeRefreshRate= */ 0f);
+        return new RefreshRateVote.RenderVote(minFrameRate, maxFrameRate);
     }
 
     static Vote forSize(int width, int height) {
-        return new Vote(/* minWidth= */ width, /* minHeight= */ height,
-                width, height,
-                /* minPhysicalRefreshRate= */ 0,
-                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
-                /* minRenderFrameRate= */ 0,
-                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
-                /* disableRefreshRateSwitching= */ false,
-                /* baseModeRefreshRate= */ 0f);
+        return new SizeVote(width, height, width, height);
     }
 
     static Vote forSizeAndPhysicalRefreshRatesRange(int minWidth, int minHeight,
             int width, int height, float minRefreshRate, float maxRefreshRate) {
-        return new Vote(minWidth, minHeight,
-                width, height,
-                minRefreshRate,
-                maxRefreshRate,
-                /* minRenderFrameRate= */ 0,
-                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
-                /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate,
-                /* baseModeRefreshRate= */ 0f);
+        return new CombinedVote(
+                List.of(
+                        new SizeVote(width, height, minWidth, minHeight),
+                        new RefreshRateVote.PhysicalVote(minRefreshRate, maxRefreshRate),
+                        new DisableRefreshRateSwitchingVote(minRefreshRate == maxRefreshRate)
+                )
+        );
     }
 
     static Vote forDisableRefreshRateSwitching() {
-        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
-                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
-                /* minPhysicalRefreshRate= */ 0,
-                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
-                /* minRenderFrameRate= */ 0,
-                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
-                /* disableRefreshRateSwitching= */ true,
-                /* baseModeRefreshRate= */ 0f);
+        return new DisableRefreshRateSwitchingVote(true);
     }
 
     static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
-        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
-                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
-                /* minPhysicalRefreshRate= */ 0,
-                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
-                /* minRenderFrameRate= */ 0,
-                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
-                /* disableRefreshRateSwitching= */ false,
-                /* baseModeRefreshRate= */ baseModeRefreshRate);
-    }
-
-    private Vote(int minWidth, int minHeight,
-            int width, int height,
-            float minPhysicalRefreshRate,
-            float maxPhysicalRefreshRate,
-            float minRenderFrameRate,
-            float maxRenderFrameRate,
-            boolean disableRefreshRateSwitching,
-            float baseModeRefreshRate) {
-        this.minWidth = minWidth;
-        this.minHeight = minHeight;
-        this.width = width;
-        this.height = height;
-        this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
-                new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
-                new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
-        this.disableRefreshRateSwitching = disableRefreshRateSwitching;
-        this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
+        return new BaseModeRefreshRateVote(baseModeRefreshRate);
     }
 
     static String priorityToString(int priority) {
@@ -291,33 +208,4 @@
                 return Integer.toString(priority);
         }
     }
-
-    @Override
-    public String toString() {
-        return "Vote: {"
-                + "minWidth: " + minWidth + ", minHeight: " + minHeight
-                + ", width: " + width + ", height: " + height
-                + ", refreshRateRanges: " + refreshRateRanges
-                + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching
-                + ", appRequestBaseModeRefreshRate: "  + appRequestBaseModeRefreshRate + "}";
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(minWidth, minHeight, width, height, refreshRateRanges,
-                disableRefreshRateSwitching, appRequestBaseModeRefreshRate);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof Vote)) return false;
-        final var vote = (Vote) o;
-        return  minWidth == vote.minWidth && minHeight == vote.minHeight
-                && width == vote.width && height == vote.height
-                && disableRefreshRateSwitching == vote.disableRefreshRateSwitching
-                && Float.compare(vote.appRequestBaseModeRefreshRate,
-                        appRequestBaseModeRefreshRate) == 0
-                && refreshRateRanges.equals(vote.refreshRateRanges);
-    }
 }
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 49c587a..95fb8fc 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -157,13 +157,19 @@
         }
     }
 
-    private int getMaxPhysicalRefreshRate(@Nullable Vote vote) {
+    private static int getMaxPhysicalRefreshRate(@Nullable Vote vote) {
         if (vote == null) {
             return -1;
-        } else if (vote.refreshRateRanges.physical.max == Float.POSITIVE_INFINITY) {
-            return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable
+        } else if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) {
+            return (int) physicalVote.mMaxRefreshRate;
+        } else if (vote instanceof CombinedVote combinedVote) {
+            return combinedVote.mVotes.stream()
+                    .filter(v -> v instanceof RefreshRateVote.PhysicalVote)
+                    .map(pv -> (int) (((RefreshRateVote.PhysicalVote) pv).mMaxRefreshRate))
+                    .min(Integer::compare)
+                    .orElse(1000); // for visualisation
         }
-        return (int) vote.refreshRateRanges.physical.max;
+        return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable
     }
 
     interface Listener {
diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS
new file mode 100644
index 0000000..535a750
--- /dev/null
+++ b/services/core/java/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
new file mode 100644
index 0000000..606a6be
--- /dev/null
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+    name: "pin_webview"
+    namespace: "system_performance"
+    description: "This flag controls if webview should be pinned in memory."
+    bug: "307594624"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index c01bc20..f992a23 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -921,6 +921,9 @@
      * port id.
      */
     int portIdToPath(int portId) {
+        if (portId == Constants.CEC_SWITCH_HOME) {
+            return getPhysicalAddress();
+        }
         HdmiPortInfo portInfo = getPortInfo(portId);
         if (portInfo == null) {
             Slog.e(TAG, "Cannot find the port info: " + portId);
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index aab491e..8e0289e 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -47,9 +47,6 @@
     private final NativeInputManagerService mNative;
     private final Map<Uri, Consumer<String /* reason*/>> mObservers;
 
-    // Cache prevent notifying same KeyRepeatInfo data to native code multiple times.
-    private KeyRepeatInfo mLastKeyRepeatInfoSettingsUpdate;
-
     InputSettingsObserver(Context context, Handler handler, InputManagerService service,
             NativeInputManagerService nativeIms) {
         super(handler);
@@ -84,9 +81,9 @@
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES),
                         (reason) -> updateShowKeyPresses()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_TIMEOUT_MS),
-                        (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())),
+                        (reason) -> updateKeyRepeatInfo()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS),
-                        (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())),
+                        (reason) -> updateKeyRepeatInfo()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT),
                         (reason) -> updateShowRotaryInput()));
     }
@@ -182,46 +179,32 @@
     }
 
     private void updateLongPressTimeout(String reason) {
-        final int longPressTimeoutValue = getLatestLongPressTimeoutValue();
-
-        // Before the key repeat timeout was introduced, some users relied on changing
-        // LONG_PRESS_TIMEOUT settings to also change the key repeat timeout. To support this
-        // backward compatibility, we'll preemptively update key repeat info here, in case where
-        // key repeat timeout was never set, and user is still relying on long press timeout value.
-        updateKeyRepeatInfo(longPressTimeoutValue);
+        // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value.
+        final int longPressTimeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
+                UserHandle.USER_CURRENT);
 
         final boolean featureEnabledFlag =
                 DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
                         DEEP_PRESS_ENABLED, true /* default */);
         final boolean enabled =
                 featureEnabledFlag
-                        && longPressTimeoutValue <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+                        && longPressTimeoutMs <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
         Log.i(TAG, (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
                 + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
-                + ", long press timeout = " + longPressTimeoutValue);
+                + ", long press timeout = " + longPressTimeoutMs + " ms");
         mNative.setMotionClassifierEnabled(enabled);
     }
 
-    private void updateKeyRepeatInfo(int fallbackKeyRepeatTimeoutValue) {
-        // Not using ViewConfiguration.getKeyRepeatTimeout here because it may return a stale value.
+    private void updateKeyRepeatInfo() {
+        // Use ViewConfiguration getters only as fallbacks because they may return stale values.
         final int timeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.KEY_REPEAT_TIMEOUT_MS, fallbackKeyRepeatTimeoutValue,
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(),
                 UserHandle.USER_CURRENT);
         final int delayMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(),
                 UserHandle.USER_CURRENT);
-        if (mLastKeyRepeatInfoSettingsUpdate == null || !mLastKeyRepeatInfoSettingsUpdate.isEqualTo(
-                timeoutMs, delayMs)) {
-            mNative.setKeyRepeatConfiguration(timeoutMs, delayMs);
-            mLastKeyRepeatInfoSettingsUpdate = new KeyRepeatInfo(timeoutMs, delayMs);
-        }
-    }
-
-    // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value.
-    private int getLatestLongPressTimeoutValue() {
-        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
-                UserHandle.USER_CURRENT);
+        mNative.setKeyRepeatConfiguration(timeoutMs, delayMs);
     }
 
     private void updateMaximumObscuringOpacityForTouch() {
@@ -233,19 +216,4 @@
         }
         mNative.setMaximumObscuringOpacityForTouch(opacity);
     }
-
-    private static class KeyRepeatInfo {
-        private final int mKeyRepeatTimeoutMs;
-        private final int mKeyRepeatDelayMs;
-
-        private KeyRepeatInfo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) {
-            this.mKeyRepeatTimeoutMs = keyRepeatTimeoutMs;
-            this.mKeyRepeatDelayMs = keyRepeatDelayMs;
-        }
-
-        public boolean isEqualTo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) {
-            return mKeyRepeatTimeoutMs == keyRepeatTimeoutMs
-                    && mKeyRepeatDelayMs == keyRepeatDelayMs;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index a46d719..14daf62 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -56,9 +56,13 @@
     public abstract void setInteractive(boolean interactive);
 
     /**
-     * Hides the current input method, if visible.
+     * Hides the input methods for all the users, if visible.
+     *
+     * @param reason               the reason for hiding the current input method
+     * @param originatingDisplayId the display ID the request is originated
      */
-    public abstract void hideCurrentInputMethod(@SoftInputShowHideReason int reason);
+    public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason,
+            int originatingDisplayId);
 
     /**
      * Returns the list of installed input methods for the specified user.
@@ -210,7 +214,8 @@
                 }
 
                 @Override
-                public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) {
+                public void hideAllInputMethods(@SoftInputShowHideReason int reason,
+                        int originatingDisplayId) {
                 }
 
                 @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6cc0693..ddb32fe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -226,7 +226,7 @@
 
     private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
 
-    private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
+    private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
     private static final int MSG_REMOVE_IME_SURFACE = 1060;
     private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
     private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
@@ -4835,7 +4835,7 @@
 
             // ---------------------------------------------------------
 
-            case MSG_HIDE_CURRENT_INPUT_METHOD:
+            case MSG_HIDE_ALL_INPUT_METHODS:
                 synchronized (ImfLock.class) {
                     final @SoftInputShowHideReason int reason = (int) msg.obj;
                     hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
@@ -5591,9 +5591,10 @@
         }
 
         @Override
-        public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) {
-            mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
-            mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget();
+        public void hideAllInputMethods(@SoftInputShowHideReason int reason,
+                int originatingDisplayId) {
+            mHandler.removeMessages(MSG_HIDE_ALL_INPUT_METHODS);
+            mHandler.obtainMessage(MSG_HIDE_ALL_INPUT_METHODS, reason).sendToTarget();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index f49fa6e..0185190 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -86,8 +86,7 @@
                     continue;
                 }
             }
-            if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode)
-                    || mode.equalsIgnoreCase(subtype.getMode())) {
+            if (TextUtils.isEmpty(mode) || mode.equalsIgnoreCase(subtype.getMode())) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 360a6a7..6bdfae2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -110,7 +110,7 @@
 
     @Override
     @NonNull
-    public synchronized MediaRoute2Info getDeviceRoute() {
+    public synchronized MediaRoute2Info getSelectedRoute() {
         if (mSelectedRoute != null) {
             return mSelectedRoute;
         }
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 7876095..0fdaaa7 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -72,13 +72,9 @@
      */
     boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
 
-    /**
-     * Returns currently selected device (built-in or wired) route.
-     *
-     * @return non-null device route.
-     */
+    /** Returns the currently selected device (built-in or wired) route. */
     @NonNull
-    MediaRoute2Info getDeviceRoute();
+    MediaRoute2Info getSelectedRoute();
 
     /**
      * Updates device route volume.
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 6ba40ae..65874e2 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -107,7 +107,7 @@
 
     @Override
     @NonNull
-    public synchronized MediaRoute2Info getDeviceRoute() {
+    public synchronized MediaRoute2Info getSelectedRoute() {
         return mDeviceRoute;
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 6a43697..4821fbe 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2486,6 +2486,13 @@
         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
                 long uniqueRequestId, int reason) {
             if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) {
+                Slog.w(
+                        TAG,
+                        TextUtils.formatSimple(
+                                "onRequestFailedOnHandler | Finished handling session creation"
+                                    + " request failed for provider: %s, uniqueRequestId: %d,"
+                                    + " reason: %d",
+                                provider.getUniqueId(), uniqueRequestId, reason));
                 return;
             }
 
@@ -2515,6 +2522,12 @@
 
             if (matchingRequest == null) {
                 // The failure is not about creating a session.
+                Slog.w(
+                        TAG,
+                        TextUtils.formatSimple(
+                                "handleSessionCreationRequestFailed | No matching request found for"
+                                    + " provider: %s, uniqueRequestId: %d, reason: %d",
+                                provider.getUniqueId(), uniqueRequestId, reason));
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index a158b18..994d3ca 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -76,6 +76,7 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import com.android.server.LocalServices;
@@ -348,16 +349,19 @@
         } else {
             if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
                 if (DEBUG) {
-                    Log.d(TAG, "Session does not support volume adjustment");
+                    Slog.d(TAG, "Session does not support volume adjustment");
                 }
             } else if (direction == AudioManager.ADJUST_TOGGLE_MUTE
                     || direction == AudioManager.ADJUST_MUTE
                     || direction == AudioManager.ADJUST_UNMUTE) {
-                Log.w(TAG, "Muting remote playback is not supported");
+                Slog.w(TAG, "Muting remote playback is not supported");
             } else {
                 if (DEBUG) {
-                    Log.w(TAG, "adjusting volume, pkg=" + packageName + ", asSystemService="
-                            + asSystemService + ", dir=" + direction);
+                    Slog.w(
+                            TAG,
+                            "adjusting volume, pkg=" + packageName
+                                    + ", asSystemService=" + asSystemService
+                                    + ", dir=" + direction);
                 }
                 mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction);
 
@@ -371,8 +375,10 @@
                 }
 
                 if (DEBUG) {
-                    Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
-                            + mMaxVolume);
+                    Slog.d(
+                            TAG,
+                            "Adjusted optimistic volume to " + mOptimisticVolume
+                                    + " max is " + mMaxVolume);
                 }
             }
             // Always notify, even if the volume hasn't changed. This is important to ensure that
@@ -388,23 +394,33 @@
         if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
             int stream = getVolumeStream(mAudioAttrs);
             final int volumeValue = value;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags,
-                                opPackageName, uid, pid,
-                                mContext.getApplicationInfo().targetSdkVersion);
-                    } catch (IllegalArgumentException | SecurityException e) {
-                        Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue
-                                + ", flags=" + flags, e);
-                    }
-                }
-            });
+            mHandler.post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                mAudioManager.setStreamVolumeForUid(
+                                        stream,
+                                        volumeValue,
+                                        flags,
+                                        opPackageName,
+                                        uid,
+                                        pid,
+                                        mContext.getApplicationInfo().targetSdkVersion);
+                            } catch (IllegalArgumentException | SecurityException e) {
+                                Slog.e(
+                                        TAG,
+                                        "Cannot set volume: stream=" + stream
+                                                + ", value=" + volumeValue
+                                                + ", flags=" + flags,
+                                        e);
+                            }
+                        }
+                    });
         } else {
             if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
                 if (DEBUG) {
-                    Log.d(TAG, "Session does not support setting volume");
+                    Slog.d(TAG, "Session does not support setting volume");
                 }
             } else {
                 value = Math.max(0, Math.min(value, mMaxVolume));
@@ -419,8 +435,10 @@
                 }
 
                 if (DEBUG) {
-                    Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
-                            + mMaxVolume);
+                    Slog.d(
+                            TAG,
+                            "Set optimistic volume to " + mOptimisticVolume
+                                    + " max is " + mMaxVolume);
                 }
             }
             // Always notify, even if the volume hasn't changed.
@@ -527,12 +545,27 @@
     @Override
     public boolean canHandleVolumeKey() {
         if (isPlaybackTypeLocal()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Local MediaSessionRecord can handle volume key");
+            }
             return true;
         }
         if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
+            if (DEBUG) {
+                Slog.d(
+                        TAG,
+                        "Local MediaSessionRecord with FIXED volume control can't handle volume"
+                                + " key");
+            }
             return false;
         }
         if (mVolumeAdjustmentForRemoteGroupSessions) {
+            if (DEBUG) {
+                Slog.d(
+                        TAG,
+                        "Volume adjustment for remote group sessions allowed so MediaSessionRecord"
+                                + " can handle volume key");
+            }
             return true;
         }
         // See b/228021646 for details.
@@ -540,7 +573,18 @@
         List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
         boolean foundNonSystemSession = false;
         boolean remoteSessionAllowVolumeAdjustment = true;
+        if (DEBUG) {
+            Slog.d(
+                    TAG,
+                    "Found "
+                            + sessions.size()
+                            + " routing sessions for package name "
+                            + mPackageName);
+        }
         for (RoutingSessionInfo session : sessions) {
+            if (DEBUG) {
+                Slog.d(TAG, "Found routingSessionInfo: " + session);
+            }
             if (!session.isSystemSession()) {
                 foundNonSystemSession = true;
                 if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
@@ -549,9 +593,14 @@
             }
         }
         if (!foundNonSystemSession) {
-            Log.d(TAG, "Package " + mPackageName
-                    + " has a remote media session but no associated routing session");
+            if (DEBUG) {
+                Slog.d(
+                        TAG,
+                        "Package " + mPackageName
+                                + " has a remote media session but no associated routing session");
+            }
         }
+
         return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
     }
 
@@ -637,8 +686,11 @@
             final boolean asSystemService, final boolean useSuggested,
             final int previousFlagPlaySound) {
         if (DEBUG) {
-            Log.w(TAG, "adjusting local volume, stream=" + stream + ", dir=" + direction
-                    + ", asSystemService=" + asSystemService + ", useSuggested=" + useSuggested);
+            Slog.w(
+                    TAG,
+                    "adjusting local volume, stream=" + stream + ", dir=" + direction
+                            + ", asSystemService=" + asSystemService
+                            + ", useSuggested=" + useSuggested);
         }
         // Must use opPackageName for adjusting volumes with UID.
         final String opPackageName;
@@ -653,40 +705,61 @@
             uid = callingUid;
             pid = callingPid;
         }
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    if (useSuggested) {
-                        if (AudioSystem.isStreamActive(stream, 0)) {
-                            mAudioManager.adjustSuggestedStreamVolumeForUid(stream,
-                                    direction, flags, opPackageName, uid, pid,
-                                    mContext.getApplicationInfo().targetSdkVersion);
-                        } else {
-                            mAudioManager.adjustSuggestedStreamVolumeForUid(
-                                    AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
-                                    flags | previousFlagPlaySound, opPackageName, uid, pid,
-                                    mContext.getApplicationInfo().targetSdkVersion);
+        mHandler.post(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            if (useSuggested) {
+                                if (AudioSystem.isStreamActive(stream, 0)) {
+                                    mAudioManager.adjustSuggestedStreamVolumeForUid(
+                                            stream,
+                                            direction,
+                                            flags,
+                                            opPackageName,
+                                            uid,
+                                            pid,
+                                            mContext.getApplicationInfo().targetSdkVersion);
+                                } else {
+                                    mAudioManager.adjustSuggestedStreamVolumeForUid(
+                                            AudioManager.USE_DEFAULT_STREAM_TYPE,
+                                            direction,
+                                            flags | previousFlagPlaySound,
+                                            opPackageName,
+                                            uid,
+                                            pid,
+                                            mContext.getApplicationInfo().targetSdkVersion);
+                                }
+                            } else {
+                                mAudioManager.adjustStreamVolumeForUid(
+                                        stream,
+                                        direction,
+                                        flags,
+                                        opPackageName,
+                                        uid,
+                                        pid,
+                                        mContext.getApplicationInfo().targetSdkVersion);
+                            }
+                        } catch (IllegalArgumentException | SecurityException e) {
+                            Slog.e(
+                                    TAG,
+                                    "Cannot adjust volume: direction=" + direction
+                                            + ", stream=" + stream + ", flags=" + flags
+                                            + ", opPackageName=" + opPackageName + ", uid=" + uid
+                                            + ", useSuggested=" + useSuggested
+                                            + ", previousFlagPlaySound=" + previousFlagPlaySound,
+                                    e);
                         }
-                    } else {
-                        mAudioManager.adjustStreamVolumeForUid(stream, direction, flags,
-                                opPackageName, uid, pid,
-                                mContext.getApplicationInfo().targetSdkVersion);
                     }
-                } catch (IllegalArgumentException | SecurityException e) {
-                    Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
-                            + stream + ", flags=" + flags + ", opPackageName=" + opPackageName
-                            + ", uid=" + uid + ", useSuggested=" + useSuggested
-                            + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
-                }
-            }
-        });
+                });
     }
 
     private void logCallbackException(
             String msg, ISessionControllerCallbackHolder holder, Exception e) {
-        Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
-                + ", exception=" + e);
+        Slog.v(
+                TAG,
+                msg + ", this=" + this + ", callback package=" + holder.mPackageName
+                        + ", exception=" + e);
     }
 
     private void pushPlaybackStateUpdate() {
@@ -1083,7 +1156,9 @@
                         throw new IllegalArgumentException(
                                 "The media button receiver cannot be set to an activity.");
                     } else {
-                        Log.w(TAG, "Ignoring invalid media button receiver targeting an activity.");
+                        Slog.w(
+                                TAG,
+                                "Ignoring invalid media button receiver targeting an activity.");
                         return;
                     }
                 }
@@ -1119,7 +1194,7 @@
                     if (CompatChanges.isChangeEnabled(THROW_FOR_INVALID_BROADCAST_RECEIVER, uid)) {
                         throw new IllegalArgumentException("Invalid component name: " + receiver);
                     } else {
-                        Log.w(
+                        Slog.w(
                                 TAG,
                                 "setMediaButtonBroadcastReceiver(): "
                                         + "Ignoring invalid component name="
@@ -1258,7 +1333,7 @@
                 if (attributes != null) {
                     mAudioAttrs = attributes;
                 } else {
-                    Log.e(TAG, "Received null audio attributes, using existing attributes");
+                    Slog.e(TAG, "Received null audio attributes, using existing attributes");
                 }
             }
             if (typeChanged) {
@@ -1320,7 +1395,7 @@
                 }
                 return true;
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in sendMediaRequest.", e);
+                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
             }
             return false;
         }
@@ -1343,7 +1418,7 @@
                 }
                 return true;
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in sendMediaRequest.", e);
+                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
             }
             return false;
         }
@@ -1356,7 +1431,7 @@
                         pid, uid, packageName, reason);
                 mCb.onCommand(packageName, pid, uid, command, args, cb);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in sendCommand.", e);
+                Slog.e(TAG, "Remote failure in sendCommand.", e);
             }
         }
 
@@ -1368,7 +1443,7 @@
                         pid, uid, packageName, reason);
                 mCb.onCustomAction(packageName, pid, uid, action, args);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in sendCustomAction.", e);
+                Slog.e(TAG, "Remote failure in sendCustomAction.", e);
             }
         }
 
@@ -1379,7 +1454,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPrepare(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in prepare.", e);
+                Slog.e(TAG, "Remote failure in prepare.", e);
             }
         }
 
@@ -1391,7 +1466,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in prepareFromMediaId.", e);
+                Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
             }
         }
 
@@ -1403,7 +1478,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in prepareFromSearch.", e);
+                Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
             }
         }
 
@@ -1414,7 +1489,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in prepareFromUri.", e);
+                Slog.e(TAG, "Remote failure in prepareFromUri.", e);
             }
         }
 
@@ -1425,7 +1500,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPlay(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in play.", e);
+                Slog.e(TAG, "Remote failure in play.", e);
             }
         }
 
@@ -1437,7 +1512,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in playFromMediaId.", e);
+                Slog.e(TAG, "Remote failure in playFromMediaId.", e);
             }
         }
 
@@ -1449,7 +1524,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in playFromSearch.", e);
+                Slog.e(TAG, "Remote failure in playFromSearch.", e);
             }
         }
 
@@ -1460,7 +1535,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in playFromUri.", e);
+                Slog.e(TAG, "Remote failure in playFromUri.", e);
             }
         }
 
@@ -1471,7 +1546,7 @@
                         pid, uid, packageName, reason);
                 mCb.onSkipToTrack(packageName, pid, uid, id);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in skipToTrack", e);
+                Slog.e(TAG, "Remote failure in skipToTrack", e);
             }
         }
 
@@ -1482,7 +1557,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPause(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in pause.", e);
+                Slog.e(TAG, "Remote failure in pause.", e);
             }
         }
 
@@ -1493,7 +1568,7 @@
                         pid, uid, packageName, reason);
                 mCb.onStop(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in stop.", e);
+                Slog.e(TAG, "Remote failure in stop.", e);
             }
         }
 
@@ -1504,7 +1579,7 @@
                         pid, uid, packageName, reason);
                 mCb.onNext(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in next.", e);
+                Slog.e(TAG, "Remote failure in next.", e);
             }
         }
 
@@ -1515,7 +1590,7 @@
                         pid, uid, packageName, reason);
                 mCb.onPrevious(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in previous.", e);
+                Slog.e(TAG, "Remote failure in previous.", e);
             }
         }
 
@@ -1526,7 +1601,7 @@
                         pid, uid, packageName, reason);
                 mCb.onFastForward(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in fastForward.", e);
+                Slog.e(TAG, "Remote failure in fastForward.", e);
             }
         }
 
@@ -1537,7 +1612,7 @@
                         pid, uid, packageName, reason);
                 mCb.onRewind(packageName, pid, uid);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in rewind.", e);
+                Slog.e(TAG, "Remote failure in rewind.", e);
             }
         }
 
@@ -1548,7 +1623,7 @@
                         pid, uid, packageName, reason);
                 mCb.onSeekTo(packageName, pid, uid, pos);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in seekTo.", e);
+                Slog.e(TAG, "Remote failure in seekTo.", e);
             }
         }
 
@@ -1559,7 +1634,7 @@
                         pid, uid, packageName, reason);
                 mCb.onRate(packageName, pid, uid, rating);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in rate.", e);
+                Slog.e(TAG, "Remote failure in rate.", e);
             }
         }
 
@@ -1570,7 +1645,7 @@
                         pid, uid, packageName, reason);
                 mCb.onSetPlaybackSpeed(packageName, pid, uid, speed);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in setPlaybackSpeed.", e);
+                Slog.e(TAG, "Remote failure in setPlaybackSpeed.", e);
             }
         }
 
@@ -1587,7 +1662,7 @@
                     mCb.onAdjustVolume(packageName, pid, uid, direction);
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in adjustVolume.", e);
+                Slog.e(TAG, "Remote failure in adjustVolume.", e);
             }
         }
 
@@ -1598,7 +1673,7 @@
                         pid, uid, packageName, reason);
                 mCb.onSetVolumeTo(packageName, pid, uid, value);
             } catch (RemoteException e) {
-                Log.e(TAG, "Remote failure in setVolumeTo.", e);
+                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
             }
         }
 
@@ -1641,8 +1716,10 @@
                         cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb));
                     mControllerCallbackHolders.add(holder);
                     if (DEBUG) {
-                        Log.d(TAG, "registering controller callback " + cb + " from controller"
-                                + packageName);
+                        Slog.d(
+                                TAG,
+                                "registering controller callback " + cb
+                                        + " from controller" + packageName);
                     }
                     // Avoid callback leaks
                     try {
@@ -1651,7 +1728,7 @@
                         cb.asBinder().linkToDeath(holder.mDeathMonitor, 0);
                     } catch (RemoteException e) {
                         unregisterCallback(cb);
-                        Log.w(TAG, "registerCallback failed to linkToDeath", e);
+                        Slog.w(TAG, "registerCallback failed to linkToDeath", e);
                     }
                 }
             }
@@ -1666,12 +1743,12 @@
                         cb.asBinder().unlinkToDeath(
                           mControllerCallbackHolders.get(index).mDeathMonitor, 0);
                     } catch (NoSuchElementException e) {
-                        Log.w(TAG, "error unlinking to binder death", e);
+                        Slog.w(TAG, "error unlinking to binder death", e);
                     }
                     mControllerCallbackHolders.remove(index);
                 }
                 if (DEBUG) {
-                    Log.d(TAG, "unregistering callback " + cb.asBinder());
+                    Slog.d(TAG, "unregistering callback " + cb.asBinder());
                 }
             }
         }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 78077a8..c8dba80 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -228,8 +228,8 @@
             return;
         }
 
-        MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
-        if (TextUtils.equals(routeId, deviceRoute.getId())) {
+        MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
+        if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) {
             mBluetoothRouteController.transferTo(null);
         } else {
             mBluetoothRouteController.transferTo(routeId);
@@ -278,11 +278,11 @@
                 return null;
             }
 
-            MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+            MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
 
             RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
                     SYSTEM_SESSION_ID, packageName).setSystemSession(true);
-            builder.addSelectedRoute(deviceRoute.getId());
+            builder.addSelectedRoute(selectedDeviceRoute.getId());
             for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
@@ -314,7 +314,7 @@
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
 
         // We must have a device route in the provider info.
-        builder.addRoute(mDeviceRouteController.getDeviceRoute());
+        builder.addRoute(mDeviceRouteController.getSelectedRoute());
 
         for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
             builder.addRoute(route);
@@ -338,12 +338,12 @@
                     SYSTEM_SESSION_ID, "" /* clientPackageName */)
                     .setSystemSession(true);
 
-            MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
-            MediaRoute2Info selectedRoute = deviceRoute;
+            MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
+            MediaRoute2Info selectedRoute = selectedDeviceRoute;
             MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
             if (selectedBtRoute != null) {
                 selectedRoute = selectedBtRoute;
-                builder.addTransferableRoute(deviceRoute.getId());
+                builder.addTransferableRoute(selectedDeviceRoute.getId());
             }
             mSelectedRouteId = selectedRoute.getId();
             mDefaultRoute =
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 1b7d1ba..9a0b391 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -208,7 +208,7 @@
                 //    network is the system default. So, if the VPN  is up and underlying network
                 //    (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
                 //    changed to match the new default network (e.g., cell).
-                mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp);
+                mVpn.startLegacyVpnPrivileged(mProfile);
             } catch (IllegalStateException e) {
                 mAcceptedEgressIface = null;
                 Log.e(TAG, "Failed to start VPN", e);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index fe91050..d0c0543 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1249,6 +1249,21 @@
                     }
                 }
             }
+            // Remove uninstalled components from user-set list
+            final ArraySet<String> userSet = mUserSetServices.get(uninstalledUserId);
+            if (userSet != null) {
+                int numServices = userSet.size();
+                for (int i = numServices - 1; i >= 0; i--) {
+                    String pkgOrComponent = userSet.valueAt(i);
+                    if (TextUtils.equals(pkg, getPackageName(pkgOrComponent))) {
+                        userSet.removeAt(i);
+                        if (DEBUG) {
+                            Slog.v(TAG, "Removing " + pkgOrComponent
+                                    + " from user-set list; uninstalled");
+                        }
+                    }
+                }
+            }
         }
         return removed;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7e51526..aa0b9b8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -184,6 +184,7 @@
 import android.companion.ICompanionDeviceManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.LoggingOnly;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
@@ -555,7 +556,7 @@
      * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L;
 
     private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
@@ -706,7 +707,6 @@
     private boolean mNotificationEffectsEnabledForAutomotive;
     private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
     protected NotificationAttentionHelper mAttentionHelper;
-    private boolean mFlagRefactorAttentionHelper;
 
     private int mWarnRemoteViewsSizeBytes;
     private int mStripRemoteViewsSizeBytes;
@@ -829,6 +829,22 @@
             }
         }
 
+        // Removes all notifications with the specified user & package.
+        public void removePackageNotifications(String pkg, @UserIdInt int userId) {
+            synchronized (mBufferLock) {
+                Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator();
+                while (bufferIter.hasNext()) {
+                    final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
+                    if (pair.first != null
+                            && userId == pair.first.getNormalizedUserId()
+                            && pkg != null && pkg.equals(pair.first.getPackageName())
+                            && pair.first.getNotification() != null) {
+                        bufferIter.remove();
+                    }
+                }
+            }
+        }
+
         void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) {
             synchronized (mBufferLock) {
                 Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
@@ -1190,7 +1206,7 @@
         @Override
         public void onSetDisabled(int status) {
             synchronized (mNotificationLock) {
-                if (mFlagRefactorAttentionHelper) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.updateDisableNotificationEffectsLocked(status);
                 } else {
                     mDisableNotificationEffects =
@@ -1336,7 +1352,7 @@
         public void clearEffects() {
             synchronized (mNotificationLock) {
                 if (DBG) Slog.d(TAG, "clearEffects");
-                if (mFlagRefactorAttentionHelper) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.clearAttentionEffects();
                 } else {
                     clearSoundLocked();
@@ -1565,7 +1581,7 @@
                         int changedFlags = data.getFlags() ^ flags;
                         if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
                             // Suppress notification flag changed, clear any effects
-                            if (mFlagRefactorAttentionHelper) {
+                            if (Flags.refactorAttentionHelper()) {
                                 mAttentionHelper.clearEffectsLocked(key);
                             } else {
                                 clearEffectsLocked(key);
@@ -1749,8 +1765,7 @@
             if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                 // update system notification channels
                 SystemNotificationChannels.createAll(context);
-                mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid(),
-                        isCallerIsSystemOrSystemUi());
+                mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid());
                 mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
             }
         }
@@ -1903,7 +1918,6 @@
                         unhideNotificationsForPackages(pkgList, uidList);
                     }
                 }
-
                 mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList);
             }
         }
@@ -1914,7 +1928,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (!mFlagRefactorAttentionHelper) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (action.equals(Intent.ACTION_SCREEN_ON)) {
                     // Keep track of screen on/off state, but do not turn off the notification light
                     // until user passes through the lock screen or views the notification.
@@ -2030,7 +2044,7 @@
             ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
                     false, this, UserHandle.USER_ALL);
-            if (!mFlagRefactorAttentionHelper) {
+            if (!Flags.refactorAttentionHelper()) {
                 resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             }
@@ -2060,7 +2074,7 @@
 
         public void update(Uri uri) {
             ContentResolver resolver = getContext().getContentResolver();
-            if (!mFlagRefactorAttentionHelper) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
                     boolean pulseEnabled = Settings.System.getIntForUser(resolver,
                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2561,9 +2575,7 @@
 
         mToastRateLimiter = toastRateLimiter;
 
-        //Cache aconfig flag value
-        mFlagRefactorAttentionHelper = Flags.refactorAttentionHelper();
-        if (mFlagRefactorAttentionHelper) {
+        if (Flags.refactorAttentionHelper()) {
             mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
                 mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
                 mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2573,7 +2585,7 @@
         // If this is called within a test, make sure to unregister the intent receivers by
         // calling onDestroy()
         IntentFilter filter = new IntentFilter();
-        if (!mFlagRefactorAttentionHelper) {
+        if (!Flags.refactorAttentionHelper()) {
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2901,7 +2913,7 @@
             }
             registerNotificationPreferencesPullers();
             new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
-            if (mFlagRefactorAttentionHelper) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.onSystemReady();
             }
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -3026,7 +3038,7 @@
                 mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
 
         mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true,
-                Binder.getCallingUid(), isCallerIsSystemOrSystemUi());
+                Binder.getCallingUid(), isCallerSystemOrSystemUi());
         if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) {
             mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid),
                     channel.getImportance() != IMPORTANCE_NONE, true);
@@ -3074,7 +3086,7 @@
         final NotificationChannelGroup preUpdate =
                 mPreferencesHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
         mPreferencesHelper.createNotificationChannelGroup(pkg, uid, group,
-                fromApp, Binder.getCallingUid(), isCallerIsSystemOrSystemUi());
+                fromApp, Binder.getCallingUid(), isCallerSystemOrSystemUi());
         if (!fromApp) {
             maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
         }
@@ -3504,7 +3516,7 @@
             }
 
             checkCallerIsSameApp(pkg);
-            final boolean isSystemToast = isCallerIsSystemOrSystemUi()
+            final boolean isSystemToast = isCallerSystemOrSystemUi()
                     || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
             boolean isAppRenderedToast = (callback != null);
             if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
@@ -4024,11 +4036,8 @@
                 Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
                 return false;
             }
-            final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
-                    SystemUiSystemPropertiesFlags.NotificationFlags
-                            .SHOW_STICKY_HUN_FOR_DENIED_FSI);
             return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
-                    showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+                    false /* forDataDelivery */);
         }
 
         @Override
@@ -4074,7 +4083,7 @@
                         channel, true /* fromTargetApp */,
                         mConditionProviders.isPackageOrComponentAllowed(
                                 pkg, UserHandle.getUserId(uid)), Binder.getCallingUid(),
-                        isCallerIsSystemOrSystemUi());
+                        isCallerSystemOrSystemUi());
                 if (needsPolicyFileChange) {
                     mListeners.notifyNotificationChannelChanged(pkg,
                             UserHandle.getUserHandleForUid(uid),
@@ -4155,7 +4164,7 @@
                 String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
                 String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
-                    || isCallerIsSystemOrSysemUiOrShell()) {
+                    || isCallerSystemOrSystemUiOrShell()) {
                 int targetUid = -1;
                 try {
                     targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
@@ -4210,7 +4219,7 @@
         public void deleteNotificationChannel(String pkg, String channelId) {
             checkCallerIsSystemOrSameApp(pkg);
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             final int callingUser = UserHandle.getUserId(callingUid);
             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                 throw new IllegalArgumentException("Cannot delete default channel");
@@ -4222,7 +4231,8 @@
             boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
                     pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
             if (previouslyExisted) {
-                // Remove from both recent notification archive and notification history
+                // Remove from both recent notification archive (recently dismissed notifications)
+                // and notification history
                 mArchive.removeChannelNotifications(pkg, callingUser, channelId);
                 mHistoryManager.deleteNotificationChannel(pkg, callingUid, channelId);
                 mListeners.notifyNotificationChannelChanged(pkg,
@@ -4253,7 +4263,7 @@
             checkCallerIsSystemOrSameApp(pkg);
 
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             NotificationChannelGroup groupToDelete =
                     mPreferencesHelper.getNotificationChannelGroupWithChannels(
                             pkg, callingUid, groupId, false);
@@ -4462,6 +4472,10 @@
 
         @Override
         public boolean areChannelsBypassingDnd() {
+            if (android.app.Flags.modesApi()) {
+                return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels()
+                        && mPreferencesHelper.areChannelsBypassingDnd();
+            }
             return mPreferencesHelper.areChannelsBypassingDnd();
         }
 
@@ -5192,7 +5206,7 @@
         public void requestInterruptionFilterFromListener(INotificationListener token,
                 int interruptionFilter) throws RemoteException {
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mNotificationLock) {
@@ -5239,7 +5253,7 @@
         public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
             enforceSystemOrSystemUI("INotificationManager.setZenMode");
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             final long identity = Binder.clearCallingIdentity();
             try {
                 mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid,
@@ -5301,7 +5315,9 @@
 
             return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
                     "addAutomaticZenRule", Binder.getCallingUid(),
-                    isCallerIsSystemOrSystemUi());
+                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
+                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
+                            : ZenModeHelper.FROM_APP);
         }
 
         @Override
@@ -5319,7 +5335,9 @@
 
             return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
                     "updateAutomaticZenRule", Binder.getCallingUid(),
-                    isCallerIsSystemOrSystemUi());
+                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
+                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
+                            : ZenModeHelper.FROM_APP);
         }
 
         @Override
@@ -5329,7 +5347,7 @@
             enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
 
             return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule",
-                    Binder.getCallingUid(), isCallerIsSystemOrSystemUi());
+                    Binder.getCallingUid(), isCallerSystemOrSystemUi());
         }
 
         @Override
@@ -5339,7 +5357,7 @@
 
             return mZenModeHelper.removeAutomaticZenRules(packageName,
                     packageName + "|removeAutomaticZenRules", Binder.getCallingUid(),
-                    isCallerIsSystemOrSystemUi());
+                    isCallerSystemOrSystemUi());
         }
 
         @Override
@@ -5358,7 +5376,7 @@
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
 
             mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(),
-                    isCallerIsSystemOrSystemUi());
+                    isCallerSystemOrSystemUi());
         }
 
         @Override
@@ -5367,7 +5385,7 @@
             final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
             if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
 
             if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
                 mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
@@ -5462,7 +5480,7 @@
                     () -> CompatChanges.isChangeEnabled(MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES,
                             callingUid));
             return !isCompatChangeEnabled
-                    || isCallerIsSystemOrSystemUi()
+                    || isCallerSystemOrSystemUi()
                     || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
                             AssociationRequest.DEVICE_PROFILE_WATCH);
         }
@@ -5693,7 +5711,7 @@
         public void setNotificationPolicy(String pkg, Policy policy) {
             enforcePolicyAccess(pkg, "setNotificationPolicy");
             int callingUid = Binder.getCallingUid();
-            boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+            boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
 
             boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
                     && !canManageGlobalZenPolicy(pkg, callingUid);
@@ -6574,7 +6592,7 @@
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    if (mFlagRefactorAttentionHelper) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.dump(pw, "    ", filter);
                     }
                 }
@@ -7095,7 +7113,7 @@
                 }
                 mPreferencesHelper.updateNotificationChannel(
                         pkg, notificationUid, channel, false, callingUid,
-                        isCallerIsSystemOrSystemUi());
+                        isCallerSystemOrSystemUi());
                 r.updateNotificationChannel(channel);
             } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId)
                     && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
@@ -7277,28 +7295,12 @@
         notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
 
         if (notification.fullScreenIntent != null) {
-            final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
-                    SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
-            if (forceDemoteFsiToStickyHun) {
+            final AttributionSource attributionSource =
+                    new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+            final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+                    attributionSource, ai, true /* forDataDelivery */);
+            if (!canUseFullScreenIntent) {
                 makeStickyHun(notification, pkg, userId);
-            } else {
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
-                final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
-                        SystemUiSystemPropertiesFlags.NotificationFlags
-                                .SHOW_STICKY_HUN_FOR_DENIED_FSI);
-                final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
-                        attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
-                        true /* forDataDelivery */);
-                if (!canUseFullScreenIntent) {
-                    if (showStickyHunIfDenied) {
-                        makeStickyHun(notification, pkg, userId);
-                    } else {
-                        notification.fullScreenIntent = null;
-                        Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
-                                + "USE_FULL_SCREEN_INTENT permission");
-                    }
-                }
             }
         }
 
@@ -7405,27 +7407,20 @@
     }
 
     private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
-            @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+            @NonNull ApplicationInfo applicationInfo,
             boolean forDataDelivery) {
         if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
             return true;
         }
-        if (isAppOpPermission) {
-            final int permissionResult;
-            if (forDataDelivery) {
-                permissionResult = mPermissionManager.checkPermissionForDataDelivery(
-                        permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
-            } else {
-                permissionResult = mPermissionManager.checkPermissionForPreflight(
-                        permission.USE_FULL_SCREEN_INTENT, attributionSource);
-            }
-            return permissionResult == PermissionManager.PERMISSION_GRANTED;
+        final int permissionResult;
+        if (forDataDelivery) {
+            permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+                    permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
         } else {
-            final int permissionResult = getContext().checkPermission(
-                    permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
-                    attributionSource.getUid());
-            return permissionResult == PERMISSION_GRANTED;
+            permissionResult = mPermissionManager.checkPermissionForPreflight(
+                    permission.USE_FULL_SCREEN_INTENT, attributionSource);
         }
+        return permissionResult == PermissionManager.PERMISSION_GRANTED;
     }
 
     private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
@@ -7875,7 +7870,7 @@
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
                     SystemClock.elapsedRealtime());
-            if (mFlagRefactorAttentionHelper) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
@@ -8015,7 +8010,7 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker, mReason,
                             mCancellationElapsedTimeMs);
-                    if (mFlagRefactorAttentionHelper) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.updateLightsLocked();
                     } else {
                         updateLightsLocked();
@@ -8312,7 +8307,7 @@
 
                     int buzzBeepBlinkLoggingCode = 0;
                     if (!r.isHidden()) {
-                        if (mFlagRefactorAttentionHelper) {
+                        if (Flags.refactorAttentionHelper()) {
                             buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
                                 new NotificationAttentionHelper.Signals(
                                     mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9299,7 +9294,7 @@
                     || interruptiveChanged;
             if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
-                if (mFlagRefactorAttentionHelper) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.buzzBeepBlinkLocked(record,
                         new NotificationAttentionHelper.Signals(
                             mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9447,7 +9442,11 @@
             for (int i = 0; i < size; i++) {
                 final String pkg = pkgList[i];
                 final int uid = uidList[i];
-                mHistoryManager.onPackageRemoved(UserHandle.getUserId(uid), pkg);
+                final int userHandle = UserHandle.getUserId(uid);
+                // Removes this package's notifications from both recent notification archive
+                // (recently dismissed notifications) and notification history.
+                mArchive.removePackageNotifications(pkg, userHandle);
+                mHistoryManager.onPackageRemoved(userHandle, pkg);
             }
         }
         if (preferencesChanged) {
@@ -9679,7 +9678,7 @@
                 });
             }
 
-            if (mFlagRefactorAttentionHelper) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.clearEffectsLocked(canceledKey);
             } else {
                 // sound
@@ -10043,7 +10042,7 @@
                             cancellationElapsedTimeMs);
                 }
             }
-            if (mFlagRefactorAttentionHelper) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
@@ -10434,7 +10433,7 @@
     }
 
     @VisibleForTesting
-    protected boolean isCallerIsSystemOrSystemUi() {
+    protected boolean isCallerSystemOrSystemUi() {
         if (isCallerSystemOrPhone()) {
             return true;
         }
@@ -10442,12 +10441,12 @@
                 == PERMISSION_GRANTED;
     }
 
-    private boolean isCallerIsSystemOrSysemUiOrShell() {
+    private boolean isCallerSystemOrSystemUiOrShell() {
         int callingUid = Binder.getCallingUid();
         if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
             return true;
         }
-        return isCallerIsSystemOrSystemUi();
+        return isCallerSystemOrSystemUi();
     }
 
     private void checkCallerIsSystemOrShell() {
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index d2e980b..9a6ea2c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -530,16 +530,13 @@
             this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
             this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
 
-            final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
-                    .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
             final boolean hasFullScreenIntent =
                     p.r.getSbn().getNotification().fullScreenIntent != null;
 
             final boolean hasFsiRequestedButDeniedFlag =  (p.r.getSbn().getNotification().flags
                     & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
 
-            this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+            this.fsi_state = NotificationRecordLogger.getFsiState(
                     hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
 
             this.is_locked = p.r.isLocked();
@@ -587,13 +584,10 @@
      * @return FrameworkStatsLog enum of the state of the full screen intent posted with this
      * notification.
      */
-    static int getFsiState(boolean isStickyHunFlagEnabled,
-                           boolean hasFullScreenIntent,
+    static int getFsiState(boolean hasFullScreenIntent,
                            boolean hasFsiRequestedButDeniedFlag,
                            NotificationReportedEvent eventType) {
-
-        if (!isStickyHunFlagEnabled
-                || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+        if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
             // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
             // so we should log 0 when possible.
             return 0;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 783e9bb..6a7eebb 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -200,6 +200,10 @@
     private SparseBooleanArray mLockScreenShowNotifications;
     private SparseBooleanArray mLockScreenPrivateNotifications;
     private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
+    // When modes_api flag is enabled, this value only tracks whether the current user has any
+    // channels marked as "priority channels", but not necessarily whether they are permitted
+    // to bypass DND by current zen policy.
+    // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined.
     private boolean mCurrentUserHasChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
     private final boolean mShowReviewPermissionsNotification;
@@ -1866,6 +1870,7 @@
                 policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi);
     }
 
+    // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
     public boolean areChannelsBypassingDnd() {
         return mCurrentUserHasChannelsBypassingDnd;
     }
@@ -2143,10 +2148,7 @@
      * @return State of the full screen intent permission for this package.
      */
     @VisibleForTesting
-    int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
-        if (!isFlagEnabled) {
-            return 0;
-        }
+    int getFsiState(String pkg, int uid, boolean requestedFSIPermission) {
         if (!requestedFSIPermission) {
             return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
         }
@@ -2167,10 +2169,8 @@
      * the user.
      */
     @VisibleForTesting
-    boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
-                                   boolean isStickyHunFlagEnabled) {
-        if (!isStickyHunFlagEnabled
-                || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+    boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) {
+        if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
             return false;
         }
         return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
@@ -2213,22 +2213,18 @@
                     pkgsWithPermissionsToHandle.remove(key);
                 }
 
-                final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
-                        .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
                 final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
 
-                final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
-                        isStickyHunFlagEnabled);
+                final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission);
 
                 final int currentPermissionFlags = mPm.getPermissionFlags(
                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
                         UserHandle.getUserHandleForUid(r.uid));
 
                 final boolean fsiIsUserSet =
-                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
-                                isStickyHunFlagEnabled);
+                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
+                                currentPermissionFlags);
 
                 events.add(FrameworkStatsLog.buildStatsEvent(
                         PACKAGE_NOTIFICATION_PREFERENCES,
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index f56a67c..ff263d1 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
 
+import android.app.Flags;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -144,6 +145,20 @@
         REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers());
     }
 
+    // Returns whether the record is permitted to bypass DND when the zen mode is
+    // ZEN_MODE_IMPORTANT_INTERRUPTIONS. This depends on whether the record's package priority is
+    // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and, if
+    // the modes_api flag is on, whether the given policy permits priority channels to bypass.
+    // TODO: b/310620812 - simplify when modes_api is inlined.
+    private boolean canRecordBypassDnd(NotificationRecord record,
+            NotificationManager.Policy policy) {
+        boolean inPriorityChannel = record.getPackagePriority() == Notification.PRIORITY_MAX;
+        if (Flags.modesApi()) {
+            return inPriorityChannel && policy.allowPriorityChannels();
+        }
+        return inPriorityChannel;
+    }
+
     /**
      * Whether to intercept the notification based on the policy
      */
@@ -180,7 +195,7 @@
                 return true;
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
                 // allow user-prioritized packages through in priority mode
-                if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+                if (canRecordBypassDnd(record, policy)) {
                     maybeLogInterceptDecision(record, false, "priorityApp");
                     return false;
                 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a1704c6..89d8200 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,6 +27,7 @@
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
@@ -73,6 +74,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeProto;
@@ -105,6 +107,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -129,6 +133,21 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;
 
+    /** A rule addition or update that is initiated by the System or SystemUI. */
+    static final int FROM_SYSTEM_OR_SYSTEMUI = 1;
+    /** A rule addition or update that is initiated by the user (through system settings). */
+    static final int FROM_USER = 2;
+    /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */
+    static final int FROM_APP = 3;
+
+    @IntDef(prefix = { "FROM_" }, value = {
+            FROM_SYSTEM_OR_SYSTEMUI,
+            FROM_USER,
+            FROM_APP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ChangeOrigin {}
+
     // pkg|userId => uid
     @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
@@ -378,7 +397,7 @@
     }
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, boolean fromSystemOrSystemUi) {
+            String reason, int callingUid, @ChangeOrigin int origin) {
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -412,10 +431,10 @@
             }
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
-            populateZenRule(pkg, automaticZenRule, rule, true);
+            populateZenRule(pkg, automaticZenRule, rule, true, origin);
             newConfig.automaticRules.put(rule.id, rule);
             if (setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    fromSystemOrSystemUi)) {
+                    origin == FROM_SYSTEM_OR_SYSTEMUI)) {
                 return rule.id;
             } else {
                 throw new AndroidRuntimeException("Could not create rule");
@@ -424,7 +443,7 @@
     }
 
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, boolean fromSystemOrSystemUi) {
+            String reason, int callingUid, @ChangeOrigin int origin) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -452,9 +471,9 @@
                 }
             }
 
-            populateZenRule(rule.pkg, automaticZenRule, rule, false);
+            populateZenRule(rule.pkg, automaticZenRule, rule, false, origin);
             return setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    fromSystemOrSystemUi);
+                    origin == FROM_SYSTEM_OR_SYSTEMUI);
         }
     }
 
@@ -790,7 +809,7 @@
         }
     }
 
-    protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) {
+    protected void updateDefaultZenRules(int callingUid) {
         updateDefaultAutomaticRuleNames();
         synchronized (mConfigLock) {
             for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
@@ -807,7 +826,7 @@
                         // update default rule (if locale changed, name of rule will change)
                         currRule.name = defaultRule.name;
                         updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
-                                "locale changed", callingUid, fromSystemOrSystemUi);
+                                "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI);
                     }
                 }
             }
@@ -850,7 +869,11 @@
     }
 
     private static void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
-            boolean isNew) {
+            boolean isNew, @ChangeOrigin int origin) {
+        // TODO: b/308671593,b/311406021 - Handle origins more precisely:
+        //  - FROM_USER can override anything and updates bitmask of user-modified fields;
+        //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - FROM_APP can only update if not user-modified.
         if (rule.enabled != automaticZenRule.isEnabled()) {
             rule.snoozing = false;
         }
@@ -860,6 +883,12 @@
         rule.enabled = automaticZenRule.isEnabled();
         rule.modified = automaticZenRule.isModified();
         rule.zenPolicy = automaticZenRule.getZenPolicy();
+        if (Flags.modesApi()) {
+            rule.zenDeviceEffects = fixZenDeviceEffects(
+                    rule.zenDeviceEffects,
+                    automaticZenRule.getDeviceEffects(),
+                    origin);
+        }
         rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
                 automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
         rule.configurationActivity = automaticZenRule.getConfigurationActivity();
@@ -879,6 +908,50 @@
         }
     }
 
+    /** "
+     * Fix" {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
+     *
+     * <ul>
+     *     <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
+     *     intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
+     *     out; if it's an update, we preserve the previous values.
+     * </ul>
+     */
+    @Nullable
+    private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
+            @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) {
+        // TODO: b/308671593,b/311406021 - Handle origins more precisely:
+        //  - FROM_USER can override anything and updates bitmask of user-modified fields;
+        //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - FROM_APP can only update if not user-modified.
+        if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) {
+            return newEffects;
+        }
+
+        if (newEffects == null) {
+            return null;
+        }
+        if (oldEffects != null) {
+            return new ZenDeviceEffects.Builder(newEffects)
+                    .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+                    .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+                    .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+                    .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+                    .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+                    .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+                    .build();
+        } else {
+            return new ZenDeviceEffects.Builder(newEffects)
+                    .setShouldDisableAutoBrightness(false)
+                    .setShouldDisableTapToWake(false)
+                    .setShouldDisableTiltToWake(false)
+                    .setShouldDisableTouch(false)
+                    .setShouldMinimizeRadioUsage(false)
+                    .setShouldMaximizeDoze(false)
+                    .build();
+        }
+    }
+
     private static AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
         AutomaticZenRule azr;
         if (Flags.modesApi()) {
@@ -888,6 +961,7 @@
                     .setIconResId(rule.iconResId)
                     .setType(rule.type)
                     .setZenPolicy(rule.zenPolicy)
+                    .setDeviceEffects(rule.zenDeviceEffects)
                     .setEnabled(rule.enabled)
                     .setInterruptionFilter(
                             NotificationManager.zenModeToInterruptionFilter(rule.zenMode))
@@ -1016,7 +1090,7 @@
         }
         pw.printf("allow(alarms=%b,media=%b,system=%b,calls=%b,callsFrom=%s,repeatCallers=%b,"
                 + "messages=%b,messagesFrom=%s,conversations=%b,conversationsFrom=%s,"
-                        + "events=%b,reminders=%b)\n",
+                        + "events=%b,reminders=%b",
                 config.allowAlarms, config.allowMedia, config.allowSystem,
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
@@ -1024,6 +1098,10 @@
                 config.allowConversations,
                 ZenPolicy.conversationTypeToString(config.allowConversationsFrom),
                 config.allowEvents, config.allowReminders);
+        if (Flags.modesApi()) {
+            pw.printf(",priorityChannels=%b", config.allowPriorityChannels);
+        }
+        pw.printf(")\n");
         pw.print(prefix);
         pw.printf("  disallow(visualEffects=%s)\n", config.suppressedVisualEffects);
         pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index a8cba53..f985b5b 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -355,15 +355,7 @@
     private boolean computeAndWriteDigestLocked() {
         byte[] digest = computeDigestLocked(null);
         if (digest != null) {
-            FileChannel channel;
-            try {
-                channel = getBlockOutputChannel();
-            } catch (IOException e) {
-                Slog.e(TAG, "partition not available?", e);
-                return false;
-            }
-
-            try {
+            try (FileChannel channel = getBlockOutputChannel()) {
                 ByteBuffer buf = ByteBuffer.allocate(DIGEST_SIZE_BYTES);
                 buf.put(digest);
                 buf.flip();
@@ -424,8 +416,7 @@
     @VisibleForTesting
     void formatPartitionLocked(boolean setOemUnlockEnabled) {
 
-        try {
-            FileChannel channel = getBlockOutputChannel();
+        try (FileChannel channel = getBlockOutputChannel()) {
             // Format the data selectively.
             //
             // 1. write header, set length = 0
@@ -471,8 +462,7 @@
 
     private void doSetOemUnlockEnabledLocked(boolean enabled) {
 
-        try {
-            FileChannel channel = getBlockOutputChannel();
+        try (FileChannel channel = getBlockOutputChannel()) {
 
             channel.position(getBlockDeviceSize() - 1);
 
@@ -554,14 +544,6 @@
                 return (int) -maxBlockSize;
             }
 
-            FileChannel channel;
-            try {
-                channel = getBlockOutputChannel();
-            } catch (IOException e) {
-                Slog.e(TAG, "partition not available?", e);
-               return -1;
-            }
-
             ByteBuffer headerAndData = ByteBuffer.allocate(
                                            data.length + HEADER_SIZE + DIGEST_SIZE_BYTES);
             headerAndData.put(new byte[DIGEST_SIZE_BYTES]);
@@ -574,7 +556,7 @@
                     return -1;
                 }
 
-                try {
+                try (FileChannel channel = getBlockOutputChannel()) {
                     channel.write(headerAndData);
                     channel.force(true);
                 } catch (IOException e) {
@@ -831,8 +813,7 @@
                 if (!mIsWritable) {
                     return;
                 }
-                try {
-                    FileChannel channel = getBlockOutputChannel();
+                try (FileChannel channel = getBlockOutputChannel()) {
                     channel.position(offset);
                     channel.write(dataBuffer);
                     channel.force(true);
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index 50ed3b1..af6a002 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -111,6 +111,11 @@
     private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = {};
 
     /**
+     * Arbitrary size restriction for the signature, used to sign the checksums.
+     */
+    private static final int MAX_SIGNATURE_SIZE_BYTES = 35 * 1024;
+
+    /**
      * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
      */
     private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;
@@ -260,6 +265,10 @@
      */
     public static @NonNull Certificate[] verifySignature(Checksum[] checksums, byte[] signature)
             throws NoSuchAlgorithmException, IOException, SignatureException {
+        if (signature == null || signature.length > MAX_SIGNATURE_SIZE_BYTES) {
+            throw new SignatureException("Invalid signature");
+        }
+
         final byte[] blob;
         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
             writeChecksums(os, checksums);
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 79cd2a0..92d469c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -259,6 +259,19 @@
      */
     boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
             int callingUid, int userId);
+
+    /**
+     * Different from
+     * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the
+     * function returns {@code true} if:
+     * <ul>
+     * <li>The target package is not archived.
+     * <li>The package cannot be found in the device or has been uninstalled in the current user.
+     * </ul>
+     */
+    boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+            @Nullable PackageStateInternal ps,
+            int callingUid, int userId);
     /**
      * Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function
      * returns {@code true} if packages with the same shared user are all uninstalled in the current
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5e76ae5..e5c4ccc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1533,6 +1533,7 @@
                     ai, flags, state, userId);
             pi.signingInfo = ps.getSigningInfo();
             pi.signatures = getDeprecatedSignatures(pi.signingInfo.getSigningDetails(), flags);
+            pi.setArchiveTimeMillis(state.getArchiveTimeMillis());
 
             if (DEBUG_PACKAGE_INFO) {
                 Log.v(TAG, "ps.pkg is n/a for ["
@@ -2454,7 +2455,8 @@
      */
     public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
             int callingUid, @Nullable ComponentName component,
-            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall,
+            boolean filterArchived) {
         if (Process.isSdkSandboxUid(callingUid)) {
             int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
             // SDK sandbox should be able to see it's client app
@@ -2468,14 +2470,20 @@
         }
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         final boolean callerIsInstantApp = instantAppPkgName != null;
+        final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived(
+                ps.getUserStateOrDefault(userId));
         // Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access
         // these hidden application details to customize carrier apps. Also, allowing the system
         // caller accessing to application across users.
         if (ps == null
                 || (filterUninstall
-                        && !isSystemOrRootOrShell(callingUid)
-                        && !ps.isHiddenUntilInstalled()
-                        && !ps.getUserStateOrDefault(userId).isInstalled())) {
+                && !isSystemOrRootOrShell(callingUid)
+                && !ps.isHiddenUntilInstalled()
+                && !ps.getUserStateOrDefault(userId).isInstalled()
+                // Archived packages behave like uninstalled packages. So if filterUninstall is
+                // set to true, we dismiss filtering some uninstalled package only if it is
+                // archived and filterArchived is set as false.
+                && (!packageArchivedForUser || filterArchived))) {
             // If caller is instant app or sdk sandbox and ps is null, pretend the application
             // exists, but, needs to be filtered
             return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid));
@@ -2523,7 +2531,20 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
+     */
+    public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
+            int callingUid, @Nullable ComponentName component,
+            @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+        return shouldFilterApplication(
+                ps, callingUid, component, componentType, userId, filterUninstall,
+                true /* filterArchived */);
+    }
+
+    /**
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
             int callingUid, @Nullable ComponentName component,
@@ -2533,7 +2554,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(
             @Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2542,7 +2564,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
             int callingUid, int userId) {
@@ -2557,7 +2580,8 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplicationIncludingUninstalled(
             @Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2566,7 +2590,19 @@
     }
 
     /**
-     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
+     */
+    public final boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+            @Nullable PackageStateInternal ps, int callingUid, int userId) {
+        return shouldFilterApplication(
+                ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */,
+                false /* filterArchived */);
+    }
+
+    /**
+     * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+     * boolean)
      */
     public final boolean shouldFilterApplicationIncludingUninstalled(
             @NonNull SharedUserSetting sus, int callingUid, int userId) {
@@ -5014,7 +5050,7 @@
         String installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
-            if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
+            if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
                     UserHandle.getUserId(callingUid))) {
                 installerPackageName = null;
             }
@@ -5032,7 +5068,8 @@
             return InstallSource.EMPTY;
         }
 
-        if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
+        if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
+                userId)) {
             return null;
         }
 
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index f45571a..07e0ddf 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -21,6 +21,7 @@
 import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
@@ -356,6 +357,12 @@
         final DeletePackageAction action;
         synchronized (mPm.mLock) {
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps == null) {
+                if (DEBUG_REMOVE) {
+                    Slog.d(TAG, "Attempted to remove non-existent package " + packageName);
+                }
+                return false;
+            }
             final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps);
             if (PackageManagerServiceUtils.isSystemApp(ps)
                     && mPm.checkPermission(CONTROL_KEYGUARD, packageName, UserHandle.USER_SYSTEM)
@@ -439,7 +446,7 @@
         if (outInfo != null) {
             // Remember which users are affected, before the installed states are modified
             outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
-                    ? ps.queryInstalledUsers(allUserHandles, /* installed= */true)
+                    ? ps.queryUsersInstalledOrHasData(allUserHandles)
                     : new int[]{userId};
         }
 
@@ -606,6 +613,10 @@
                     firstInstallTime,
                     PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
                     archiveState);
+
+            if ((flags & DELETE_ARCHIVE) != 0) {
+                ps.modifyUserState(nextUserId).setArchiveTimeMillis(System.currentTimeMillis());
+            }
         }
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d46d559..448f215 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -154,6 +154,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
@@ -175,7 +176,6 @@
 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.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -3349,9 +3349,7 @@
             if (disabledPs == null) {
                 logCriticalInfo(Log.WARN, "System package " + packageName
                         + " no longer exists; its data will be wiped");
-                mInjector.getHandler().post(
-                        () -> mRemovePackageHelper.removePackageData(ps, userIds));
-                expectingBetter.put(ps.getPackageName(), ps.getPath());
+                mRemovePackageHelper.removePackageData(ps, userIds);
             } else {
                 // we still have a disabled system package, but, it still might have
                 // been removed. check the code path still exists and check there's
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index fc83120..5494bd9 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -50,9 +50,9 @@
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.art.model.DexoptResult;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -539,6 +539,12 @@
     }
 
     @Nullable
+    public PackageSetting getScanRequestDisabledPackageSetting() {
+        assertScanResultExists();
+        return mScanResult.mRequest.mDisabledPkgSetting;
+    }
+
+    @Nullable
     public String getRealPackageName() {
         assertScanResultExists();
         return mScanResult.mRequest.mRealPkgName;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 3f4cc4a..b80c009 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1531,7 +1531,8 @@
                 throw new ActivityNotFoundException("Activity could not be found");
             }
 
-            final Intent launchIntent = getMainActivityLaunchIntent(component, user);
+            final Intent launchIntent = getMainActivityLaunchIntent(component, user,
+                    false /* includeArchivedApps */);
             if (launchIntent == null) {
                 throw new SecurityException("Attempt to launch activity without "
                         + " category Intent.CATEGORY_LAUNCHER " + component);
@@ -1577,7 +1578,8 @@
                 return;
             }
 
-            Intent launchIntent = getMainActivityLaunchIntent(component, user);
+            Intent launchIntent = getMainActivityLaunchIntent(component, user,
+                    true /* includeArchivedApps */);
             if (launchIntent == null) {
                 throw new SecurityException("Attempt to launch activity without "
                         + " category Intent.CATEGORY_LAUNCHER " + component);
@@ -1593,7 +1595,8 @@
         /**
          * Returns the main activity launch intent for the given component package.
          */
-        private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) {
+        private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user,
+                boolean includeArchivedApps) {
             Intent launchIntent = new Intent(Intent.ACTION_MAIN);
             launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
             launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1632,6 +1635,14 @@
                         break;
                     }
                 }
+                if (!canLaunch
+                        && includeArchivedApps
+                        && Flags.archiving()
+                        && getMatchingArchivedAppActivityInfo(component, user) != null) {
+                    launchIntent.setPackage(null);
+                    launchIntent.setComponent(component);
+                    canLaunch = true;
+                }
                 if (!canLaunch) {
                     return null;
                 }
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index 6faf68d..c66a9e9 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -22,7 +22,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index d5dacce..c6e8a64 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -16,12 +16,17 @@
 
 package com.android.server.pm;
 
+import static android.app.ActivityManager.START_ABORTED;
+import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
+import static android.app.ActivityManager.START_PERMISSION_DENIED;
+import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
 import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
@@ -33,9 +38,13 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ArchivedActivityParcel;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.LauncherActivityInfo;
@@ -56,11 +65,13 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.IBinder;
 import android.os.ParcelableException;
 import android.os.Process;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ExceptionUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -68,6 +79,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -92,6 +104,9 @@
 
     private static final String TAG = "PackageArchiverService";
 
+    public static final String EXTRA_UNARCHIVE_INTENT_SENDER =
+            "android.content.pm.extra.UNARCHIVE_INTENT_SENDER";
+
     /**
      * The maximum time granted for an app store to start a foreground service when unarchival
      * is requested.
@@ -101,6 +116,8 @@
 
     private static final String ARCHIVE_ICONS_DIR = "package_archiver";
 
+    private static final String ACTION_UNARCHIVE_DIALOG = "android.intent.action.UNARCHIVE_DIALOG";
+
     private final Context mContext;
     private final PackageManagerService mPm;
 
@@ -138,6 +155,8 @@
         }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
+        verifyUninstallPermissions();
+
         CompletableFuture<ArchiveState> archiveStateFuture;
         try {
             archiveStateFuture = createArchiveState(packageName, userId);
@@ -175,11 +194,119 @@
                         });
     }
 
+    /**
+     * Starts unarchival for the package corresponding to the startActivity intent. Note that this
+     * will work only if the caller is the default/Home Launcher or if activity is started via Shell
+     * identity.
+     */
+    @NonNull
+    public int requestUnarchiveOnActivityStart(@Nullable Intent intent,
+            @Nullable String callerPackageName, int userId, int callingUid) {
+        String packageName = getPackageNameFromIntent(intent);
+        if (packageName == null) {
+            Slog.e(TAG, "packageName cannot be null for unarchival!");
+            return START_CLASS_NOT_FOUND;
+        }
+        if (callerPackageName == null) {
+            Slog.e(TAG, "callerPackageName cannot be null for unarchival!");
+            return START_CLASS_NOT_FOUND;
+        }
+        if (!isCallingPackageValid(callerPackageName, callingUid, userId)) {
+            // Return early as the calling UID does not match caller package's UID.
+            return START_CLASS_NOT_FOUND;
+        }
+        String currentLauncherPackageName = getCurrentLauncherPackageName(userId);
+        if ((currentLauncherPackageName == null || !callerPackageName.equals(
+                currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
+            // TODO(b/311619990): Remove dependency on SHELL_UID for testing
+            Slog.e(TAG, TextUtils.formatSimple(
+                    "callerPackageName: %s does not qualify for archival of package: " + "%s!",
+                    callerPackageName, packageName));
+            return START_PERMISSION_DENIED;
+        }
+        // TODO(b/302114464): Handle edge cases & also divert to a dialog based on
+        //  permissions + compat options
+        Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
+        try {
+            final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+                @Override
+                public void send(int code, Intent intent, String resolvedType,
+                        IBinder allowlistToken,
+                        IIntentReceiver finishedReceiver, String requiredPermission,
+                        Bundle options) {
+                    // TODO(b/302114464): Handle intent sender status codes
+                }
+            };
+
+            requestUnarchive(packageName, callerPackageName,
+                    new IntentSender((IIntentSender) mLocalSender), UserHandle.of(userId));
+        } catch (Throwable t) {
+            Slog.e(TAG, TextUtils.formatSimple(
+                    "Unexpected error occurred while unarchiving package %s: %s.", packageName,
+                    t.getLocalizedMessage()));
+            return START_ABORTED;
+        }
+        return START_SUCCESS;
+    }
+
+    /**
+     * Returns true if the componentName targeted by the intent corresponds to that of an archived
+     * app.
+     */
+    public boolean isIntentResolvedToArchivedApp(Intent intent, int userId) {
+        String packageName = getPackageNameFromIntent(intent);
+        if (packageName == null || intent.getComponent() == null) {
+            return false;
+        }
+        PackageState packageState = mPm.snapshotComputer().getPackageStateInternal(packageName);
+        if (packageState == null) {
+            return false;
+        }
+        PackageUserState userState = packageState.getUserStateOrDefault(userId);
+        if (!PackageArchiver.isArchived(userState)) {
+            return false;
+        }
+        List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList =
+                userState.getArchiveState().getActivityInfos();
+        for (int i = 0; i < archiveActivityInfoList.size(); i++) {
+            if (archiveActivityInfoList.get(i)
+                    .getOriginalComponentName().equals(intent.getComponent())) {
+                return true;
+            }
+        }
+        Slog.e(TAG, TextUtils.formatSimple(
+                "Package: %s is archived but component to start main activity"
+                        + " cannot be found!", packageName));
+        return false;
+    }
+
+    @Nullable
+    private String getCurrentLauncherPackageName(int userId) {
+        ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity(
+                userId);
+        if (defaultLauncherComponent != null) {
+            return defaultLauncherComponent.getPackageName();
+        }
+        return null;
+    }
+
+    private boolean isCallingPackageValid(String callingPackage, int callingUid, int userId) {
+        int packageUid;
+        packageUid = mPm.snapshotComputer().getPackageUid(callingPackage, 0L, userId);
+        if (packageUid != callingUid) {
+            Slog.w(TAG, TextUtils.formatSimple("Calling package: %s does not belong to uid: %d",
+                    callingPackage, callingUid));
+            return false;
+        }
+        return true;
+    }
+
     /** Creates archived state for the package and user. */
     private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
             throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
                 Binder.getCallingUid(), userId);
+        verifyNotSystemApp(ps.getFlags());
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
         verifyInstaller(responsibleInstallerPackage, userId);
         verifyOptOutStatus(packageName,
@@ -314,6 +441,13 @@
         return intentReceivers != null && !intentReceivers.getList().isEmpty();
     }
 
+    private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException {
+        if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+                (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+            throw new PackageManager.NameNotFoundException("System apps cannot be archived.");
+        }
+    }
+
     /**
      * Returns true if the app is archivable.
      */
@@ -335,6 +469,11 @@
             throw new ParcelableException(e);
         }
 
+        if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+                (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+            return false;
+        }
+
         if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
             return false;
         }
@@ -370,22 +509,27 @@
     void requestUnarchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
+            @NonNull IntentSender statusReceiver,
             @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
+        Objects.requireNonNull(statusReceiver);
         Objects.requireNonNull(userHandle);
 
         Computer snapshot = mPm.snapshotComputer();
         int userId = userHandle.getIdentifier();
         int binderUid = Binder.getCallingUid();
-        if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) {
+        if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
             verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
         }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "unarchiveApp");
+
         PackageStateInternal ps;
+        PackageStateInternal callerPs;
         try {
             ps = getPackageState(packageName, snapshot, binderUid, userId);
+            callerPs = getPackageState(callerPackageName, snapshot, binderUid, userId);
             verifyArchived(ps, userId);
         } catch (PackageManager.NameNotFoundException e) {
             throw new ParcelableException(e);
@@ -398,7 +542,94 @@
                                     packageName)));
         }
 
-        mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
+        boolean hasInstallPackages = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.INSTALL_PACKAGES)
+                == PackageManager.PERMISSION_GRANTED;
+        // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester
+        // is not the source of the installation.
+        boolean hasRequestInstallPackages = callerPs.getAndroidPackage().getRequestedPermissions()
+                .contains(android.Manifest.permission.REQUEST_INSTALL_PACKAGES);
+        if (!hasInstallPackages && !hasRequestInstallPackages) {
+            throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
+                    + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
+                    + "an unarchival.");
+        }
+
+        if (!hasInstallPackages) {
+            requestUnarchiveConfirmation(packageName, statusReceiver);
+            return;
+        }
+
+        // TODO(b/311709794) Check that the responsible installer has INSTALL_PACKAGES or
+        // OPSTR_REQUEST_INSTALL_PACKAGES too. Edge case: In reality this should always be the case,
+        // unless a user has disabled the permission after archiving an app.
+
+        int draftSessionId;
+        try {
+            draftSessionId = Binder.withCleanCallingIdentity(() ->
+                    createDraftSession(packageName, installerPackage, statusReceiver, userId));
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof IOException) {
+                throw ExceptionUtils.wrap((IOException) e.getCause());
+            } else {
+                throw e;
+            }
+        }
+
+        mPm.mHandler.post(
+                () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
+    }
+
+    private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver) {
+        final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG);
+        dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver);
+        dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+
+        final Intent broadcastIntent = new Intent();
+        broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+        broadcastIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS,
+                PackageInstaller.STATUS_PENDING_USER_ACTION);
+        broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
+        sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
+    }
+
+    private void verifyUninstallPermissions() {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+                Manifest.permission.REQUEST_DELETE_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
+                    + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
+                    + "an archival.");
+        }
+    }
+
+    private int createDraftSession(String packageName, String installerPackage,
+            IntentSender statusReceiver, int userId) throws IOException {
+        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        sessionParams.setAppPackageName(packageName);
+        sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+        sessionParams.unarchiveIntentSender = statusReceiver;
+
+        int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+        // Handles case of repeated unarchival calls for the same package.
+        int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
+                sessionParams,
+                userId);
+        if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) {
+            return existingSessionId;
+        }
+
+        int sessionId = mPm.mInstallerService.createSessionInternal(
+                sessionParams,
+                installerPackage, mContext.getAttributionTag(),
+                installerUid,
+                userId);
+        // TODO(b/297358628) Also cleanup sessions upon device restart.
+        mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId),
+                getUnarchiveForegroundTimeout());
+        return sessionId;
     }
 
     /**
@@ -461,7 +692,7 @@
                 cloudDrawable.getIntrinsicWidth(),
                 cloudDrawable.getIntrinsicHeight());
         LayerDrawable layerDrawable =
-                new LayerDrawable(new Drawable[] {appIconDrawable, cloudDrawable});
+                new LayerDrawable(new Drawable[]{appIconDrawable, cloudDrawable});
         final int iconSize = mContext.getSystemService(
                 ActivityManager.class).getLauncherLargeIconSize();
         Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize);
@@ -487,10 +718,11 @@
                     android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND},
             conditional = true)
     private void unarchiveInternal(String packageName, UserHandle userHandle,
-            String installerPackage) {
+            String installerPackage, int unarchiveId) {
         int userId = userHandle.getIdentifier();
         Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
         unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, unarchiveId);
         unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
         unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
                 userId == UserHandle.USER_ALL);
@@ -603,20 +835,25 @@
             String message) {
         Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName,
                 message));
-        final Intent fillIn = new Intent();
-        fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
-        fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
-        fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
+        final Intent intent = new Intent();
+        intent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+        intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+        intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
+        sendIntent(statusReceiver, packageName, message, intent);
+    }
+
+    private void sendIntent(IntentSender statusReceiver, String packageName, String message,
+            Intent intent) {
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityStartMode(
                     MODE_BACKGROUND_ACTIVITY_START_DENIED);
-            statusReceiver.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+            statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null,
                     /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
         } catch (IntentSender.SendIntentException e) {
             Slog.e(
                     TAG,
-                    TextUtils.formatSimple("Failed to send failure status for %s with message %s",
+                    TextUtils.formatSimple("Failed to send status for %s with message %s",
                             packageName, message),
                     e);
         }
@@ -659,6 +896,20 @@
         return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
     }
 
+    @Nullable
+    private static String getPackageNameFromIntent(@Nullable Intent intent) {
+        if (intent == null) {
+            return null;
+        }
+        if (intent.getPackage() != null) {
+            return intent.getPackage();
+        }
+        if (intent.getComponent() != null) {
+            return intent.getComponent().getPackageName();
+        }
+        return null;
+    }
+
     /**
      * Creates serializable archived activities from existing ArchiveState.
      */
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 98eee4d..882e05d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,9 +16,17 @@
 
 package com.android.server.pm;
 
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
 
@@ -35,6 +43,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -54,6 +63,7 @@
 import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -69,6 +79,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelableException;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -633,17 +644,18 @@
                         + "to use a data loader");
             }
 
+            // Draft sessions cannot be created through the public API.
+            params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE_DRAFT;
             return createSessionInternal(params, installerPackageName, callingAttributionTag,
-                    userId);
+                    Binder.getCallingUid(), userId);
         } catch (IOException e) {
             throw ExceptionUtils.wrap(e);
         }
     }
 
-    private int createSessionInternal(SessionParams params, String installerPackageName,
-            String installerAttributionTag, int userId)
+    int createSessionInternal(SessionParams params, String installerPackageName,
+            String installerAttributionTag, int callingUid, int userId)
             throws IOException {
-        final int callingUid = Binder.getCallingUid();
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
 
@@ -692,7 +704,7 @@
             // initiatingPackageName
             installerPackageName = SHELL_PACKAGE_NAME;
         } else {
-            if (callingUid != Process.SYSTEM_UID) {
+            if (callingUid != SYSTEM_UID) {
                 // The supplied installerPackageName must always belong to the calling app.
                 mAppOps.checkPackage(callingUid, installerPackageName);
             }
@@ -707,6 +719,7 @@
 
             params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
             params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
+            params.installFlags &= ~PackageManager.INSTALL_ARCHIVED;
             params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
             if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
                     && !mPm.isCallerVerifier(snapshot, callingUid)) {
@@ -903,6 +916,16 @@
             }
         }
 
+        int requestedInstallerPackageUid = INVALID_UID;
+        if (requestedInstallerPackageName != null) {
+            requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName,
+                    0 /* flags */, userId);
+        }
+        if (requestedInstallerPackageUid == INVALID_UID) {
+            // Requested installer package is invalid, reset it
+            requestedInstallerPackageName = null;
+        }
+
         final int sessionId;
         final PackageInstallerSession session;
         synchronized (mSessions) {
@@ -923,8 +946,11 @@
                 throw new IllegalStateException(
                         "Too many historical sessions for UID " + callingUid);
             }
+            final int existingDraftSessionId =
+                    getExistingDraftSessionId(requestedInstallerPackageUid, params, userId);
 
-            sessionId = allocateSessionIdLocked();
+            sessionId = existingDraftSessionId != SessionInfo.INVALID_ID ? existingDraftSessionId
+                    : allocateSessionIdLocked();
         }
 
         final long createdMillis = System.currentTimeMillis();
@@ -945,15 +971,6 @@
                 params.forceQueryableOverride = false;
             }
         }
-        int requestedInstallerPackageUid = INVALID_UID;
-        if (requestedInstallerPackageName != null) {
-            requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName,
-                    0 /* flags */, userId);
-        }
-        if (requestedInstallerPackageUid == INVALID_UID) {
-            // Requested installer package is invalid, reset it
-            requestedInstallerPackageName = null;
-        }
 
         final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
         if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) {
@@ -988,6 +1005,68 @@
         return sessionId;
     }
 
+    int getExistingDraftSessionId(int installerUid,
+            @NonNull SessionParams sessionParams, int userId) {
+        synchronized (mSessions) {
+            return getExistingDraftSessionIdInternal(installerUid, sessionParams, userId);
+        }
+    }
+
+    @GuardedBy("mSessions")
+    private int getExistingDraftSessionIdInternal(int installerUid,
+            SessionParams sessionParams, int userId) {
+        String appPackageName = sessionParams.appPackageName;
+        if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) {
+            return SessionInfo.INVALID_ID;
+        }
+
+        PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(appPackageName,
+                SYSTEM_UID);
+        if (ps == null || !PackageArchiver.isArchived(ps.getUserStateOrDefault(userId))) {
+            return SessionInfo.INVALID_ID;
+        }
+
+        // If unarchiveId is present we match based on it. If unarchiveId is missing we
+        // choose a draft session too to ensure we don't end up with duplicate sessions
+        // if the installer doesn't set this field.
+        if (sessionParams.unarchiveId > 0) {
+            PackageInstallerSession session = mSessions.get(sessionParams.unarchiveId);
+            if (session != null
+                    && isValidDraftSession(session, appPackageName, installerUid, userId)) {
+                return session.sessionId;
+            }
+
+            return SessionInfo.INVALID_ID;
+        }
+
+        for (int i = 0; i < mSessions.size(); i++) {
+            PackageInstallerSession session = mSessions.valueAt(i);
+            if (session != null
+                    && isValidDraftSession(session, appPackageName, installerUid, userId)) {
+                return session.sessionId;
+            }
+        }
+
+        return SessionInfo.INVALID_ID;
+    }
+
+    private boolean isValidDraftSession(@NonNull PackageInstallerSession session,
+            @NonNull String appPackageName, int installerUid, int userId) {
+        return (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT) != 0
+                && appPackageName.equals(session.params.appPackageName)
+                && session.userId == userId
+                && installerUid == session.getInstallerUid();
+    }
+
+    void cleanupDraftIfUnclaimed(int sessionId) {
+        synchronized (mSessions) {
+            PackageInstallerSession session = mPm.mInstallerService.getSession(sessionId);
+            if (session != null && (session.getInstallFlags() & INSTALL_UNARCHIVE_DRAFT) != 0) {
+                session.abandon();
+            }
+        }
+    }
+
     private boolean isStagedInstallerAllowed(String installerName) {
         return SystemConfig.getInstance().getWhitelistedStagedInstallers().contains(installerName);
     }
@@ -1053,7 +1132,8 @@
     }
 
     private boolean checkOpenSessionAccess(final PackageInstallerSession session) {
-        if (session == null) {
+        if (session == null
+                || (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT) != 0) {
             return false;
         }
         if (isCallingUidOwner(session)) {
@@ -1248,10 +1328,12 @@
                 final PackageInstallerSession session = mSessions.valueAt(i);
 
                 SessionInfo info =
-                        session.generateInfoForCaller(false /*withIcon*/, Process.SYSTEM_UID);
+                        session.generateInfoForCaller(false /*withIcon*/, SYSTEM_UID);
                 if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
                         && session.userId == userId && !session.hasParentSessionId()
-                        && isCallingUidOwner(session)) {
+                        && isCallingUidOwner(session)
+                        && (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT)
+                            == 0) {
                     result.add(info);
                 }
             }
@@ -1557,8 +1639,10 @@
     public void requestUnarchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
+            @NonNull IntentSender statusReceiver,
             @NonNull UserHandle userHandle) {
-        mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+        mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver,
+                userHandle);
     }
 
     @Override
@@ -1599,20 +1683,119 @@
                 archivedPackageParcel);
 
         // Create and commit install archived session.
-        PackageInstallerSession session = null;
-        try {
-            var sessionId = createSessionInternal(params, installerPackageName,
-                    null /*installerAttributionTag*/, userId);
-            session = openSessionInternal(sessionId);
-            session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(),
-                    null /*signature*/);
-            session.commit(statusReceiver, false /*forTransfer*/);
-        } catch (IOException e) {
-            throw ExceptionUtils.wrap(e);
-        } finally {
-            if (session != null) {
-                session.close();
+        // Session belongs to the system_server and would not appear anywhere in the Public APIs.
+        Binder.withCleanCallingIdentity(() -> {
+            PackageInstallerSession session = null;
+            try {
+                var sessionId = createSessionInternal(params, installerPackageName, null
+                        /*installerAttributionTag*/, Binder.getCallingUid(), userId);
+                session = openSessionInternal(sessionId);
+                session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/,
+                        metadata.toByteArray(), null /*signature*/);
+                session.commit(statusReceiver, false /*forTransfer*/);
+            } catch (IOException e) {
+                throw ExceptionUtils.wrap(e);
+            } finally {
+                if (session != null) {
+                    session.close();
+                }
             }
+        });
+    }
+
+    // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
+    @Override
+    public void reportUnarchivalStatus(
+            int unarchiveId,
+            @UnarchivalStatus int status,
+            long requiredStorageBytes,
+            @Nullable PendingIntent userActionIntent,
+            @NonNull UserHandle userHandle) {
+        verifyReportUnarchiveStatusInput(
+                status, requiredStorageBytes, userActionIntent, userHandle);
+
+        int userId = userHandle.getIdentifier();
+        int binderUid = Binder.getCallingUid();
+
+        synchronized (mSessions) {
+            PackageInstallerSession session = mSessions.get(unarchiveId);
+            if (session == null || session.userId != userId
+                    || session.params.appPackageName == null) {
+                throw new ParcelableException(new PackageManager.NameNotFoundException(
+                        TextUtils.formatSimple(
+                                "No valid session with unarchival ID %s found for user %s.",
+                                unarchiveId, userId)));
+            }
+
+            if (!isCallingUidOwner(session)) {
+                throw new SecurityException(TextUtils.formatSimple(
+                        "The caller UID %s does not have access to the session with unarchiveId "
+                                + "%d.",
+                        binderUid, unarchiveId));
+            }
+
+            IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
+            if (unarchiveIntentSender == null) {
+                throw new IllegalStateException(
+                        TextUtils.formatSimple(
+                                "Unarchival status for ID %s has already been set or a "
+                                        + "session has been created for it already by the "
+                                        + "caller.",
+                                unarchiveId));
+            }
+
+            // Execute expensive calls outside the sync block.
+            mPm.mHandler.post(
+                    () -> notifyUnarchivalListener(status, session.params.appPackageName,
+                            unarchiveIntentSender));
+            session.params.unarchiveIntentSender = null;
+            if (status != UNARCHIVAL_OK) {
+                Binder.withCleanCallingIdentity(session::abandon);
+            }
+        }
+    }
+
+    private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes,
+            @Nullable PendingIntent userActionIntent,
+            @NonNull UserHandle userHandle) {
+        Objects.requireNonNull(userHandle);
+        if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+            Objects.requireNonNull(userActionIntent);
+        }
+        if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) {
+            throw new IllegalStateException(
+                    "Insufficient storage error set, but requiredStorageBytes unspecified.");
+        }
+        if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) {
+            throw new IllegalStateException(
+                    TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status)
+            );
+        }
+        if (!List.of(
+                UNARCHIVAL_OK,
+                UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+                UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+                UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+                UNARCHIVAL_GENERIC_ERROR).contains(status)) {
+            throw new IllegalStateException("Invalid status code passed " + status);
+        }
+    }
+
+    private void notifyUnarchivalListener(int status, String packageName,
+            IntentSender unarchiveIntentSender) {
+        final Intent fillIn = new Intent();
+        fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+        fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status);
+        // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here.
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_DENIED);
+        try {
+            unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+                    /* handler= */ null, /* requiredPermission= */ null,
+                    options.toBundle());
+        } catch (SendIntentException e) {
+            Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
         }
     }
 
@@ -2017,7 +2200,7 @@
                 // we don't scrub the data here as this is sent only to the installer several
                 // privileged system packages
                 sendSessionUpdatedBroadcast(
-                        session.generateInfoForCaller(false/*icon*/, Process.SYSTEM_UID),
+                        session.generateInfoForCaller(false/*icon*/, SYSTEM_UID),
                         session.userId);
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 52fdfd1..47d1df5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1612,7 +1612,7 @@
             try {
                 Certificate[] ignored = ApkChecksums.verifySignature(checksums, signature);
             } catch (IOException | NoSuchAlgorithmException | SignatureException e) {
-                throw new IllegalArgumentException("Can't verify signature", e);
+                throw new IllegalArgumentException("Can't verify signature: " + e.getMessage(), e);
             }
         }
 
@@ -2248,31 +2248,39 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean isInstallationAllowed(PackageStateInternal psi) {
+        if (psi == null || psi.getPkg() == null) {
+            return true;
+        }
+        if (psi.getPkg().isUpdatableSystem()) {
+            return true;
+        }
+        if (mOriginalInstallerUid == Process.ROOT_UID) {
+            Slog.w(TAG, "Overriding updatableSystem because the installer is root: "
+                    + psi.getPackageName());
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Check if this package can be installed archived.
      */
-    private static boolean isArchivedInstallationAllowed(String packageName) {
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        final PackageStateInternal existingPkgSetting = pmi.getPackageStateInternal(packageName);
-        if (existingPkgSetting == null) {
+    private static boolean isArchivedInstallationAllowed(PackageStateInternal psi) {
+        if (psi == null) {
             return true;
         }
-
         return false;
     }
 
     /**
      * Checks if the package can be installed on IncFs.
      */
-    private static boolean isIncrementalInstallationAllowed(String packageName) {
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        final PackageStateInternal existingPkgSetting = pmi.getPackageStateInternal(packageName);
-        if (existingPkgSetting == null || existingPkgSetting.getPkg() == null) {
+    private static boolean isIncrementalInstallationAllowed(PackageStateInternal psi) {
+        if (psi == null || psi.getPkg() == null) {
             return true;
         }
-
-        return !existingPkgSetting.isSystem()
-                && !existingPkgSetting.isUpdatedSystemApp();
+        return !psi.isSystem() && !psi.isUpdatedSystemApp();
     }
 
     /**
@@ -3371,6 +3379,16 @@
                         "Split " + apk.getSplitName() + " was defined multiple times");
             }
 
+            if (!apk.isUpdatableSystem()) {
+                if (mOriginalInstallerUid == Process.ROOT_UID) {
+                    Slog.w(TAG, "Overriding updatableSystem because the installer is root for: "
+                            + apk.getPackageName());
+                } else {
+                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                            "Non updatable system package can't be installed or updated");
+                }
+            }
+
             // Use first package to define unknown values
             if (mPackageName == null) {
                 mPackageName = apk.getPackageName();
@@ -3445,8 +3463,17 @@
             }
         }
 
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        final PackageStateInternal existingPkgSetting = pmi.getPackageStateInternal(mPackageName);
+
+        if (!isInstallationAllowed(existingPkgSetting)) {
+            throw new PackageManagerException(
+                    PackageManager.INSTALL_FAILED_SESSION_INVALID,
+                    "Installation of this package is not allowed.");
+        }
+
         if (isArchivedInstallation()) {
-            if (!isArchivedInstallationAllowed(mPackageName)) {
+            if (!isArchivedInstallationAllowed(existingPkgSetting)) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_SESSION_INVALID,
                         "Archived installation of this package is not allowed.");
@@ -3462,7 +3489,7 @@
         }
 
         if (isIncrementalInstallation()) {
-            if (!isIncrementalInstallationAllowed(mPackageName)) {
+            if (!isIncrementalInstallationAllowed(existingPkgSetting)) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_SESSION_INVALID,
                         "Incremental installation of this package is not allowed.");
@@ -3559,21 +3586,30 @@
                 params.setDontKillApp(false);
             }
 
+            boolean existingSplitReplacedOrRemoved = false;
             // Inherit splits if not overridden.
             if (!ArrayUtils.isEmpty(existing.getSplitNames())) {
                 for (int i = 0; i < existing.getSplitNames().length; i++) {
                     final String splitName = existing.getSplitNames()[i];
                     final File splitFile = new File(existing.getSplitApkPaths()[i]);
                     final boolean splitRemoved = removeSplitList.contains(splitName);
-                    if (!stagedSplits.contains(splitName) && !splitRemoved) {
+                    final boolean splitReplaced = stagedSplits.contains(splitName);
+                    if (!splitReplaced && !splitRemoved) {
                         inheritFileLocked(splitFile);
                         // Collect the requiredSplitTypes and staged splitTypes from splits
                         CollectionUtils.addAll(requiredSplitTypes,
                                 existing.getRequiredSplitTypes()[i]);
                         CollectionUtils.addAll(stagedSplitTypes, existing.getSplitTypes()[i]);
+                    } else {
+                        existingSplitReplacedOrRemoved = true;
                     }
                 }
             }
+            if (existingSplitReplacedOrRemoved
+                    && (params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
+                // Some splits are being replaced or removed. Make sure the app is restarted.
+                params.setDontKillApp(false);
+            }
 
             // Inherit compiled oat directory.
             final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7d3d85d..233bf4f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,8 @@
 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.pkg.AndroidPackageInternal;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.telephony.CarrierAppUtils;
@@ -220,9 +222,7 @@
 import com.android.server.pm.local.PackageManagerLocalImpl;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerService;
 import com.android.server.pm.permission.LegacyPermissionSettings;
@@ -3105,7 +3105,8 @@
     }
 
     private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot,
-            String callingPackage, int callingUid, int userId, String callingMethod) {
+            boolean quarantined, String callingPackage, int callingUid, int userId,
+            String callingMethod) {
         if (callingUid == Process.ROOT_UID
                 // Need to compare app-id to allow system dialogs access on secondary users
                 || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
@@ -3120,8 +3121,20 @@
             }
         }
 
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
-                callingMethod);
+        if (quarantined) {
+            final boolean hasQuarantineAppsPerm = mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.QUARANTINE_APPS) == PERMISSION_GRANTED;
+            // TODO: b/305256093 - In order to facilitate testing, temporarily allowing apps
+            // with SUSPEND_APPS permission to quarantine apps. Remove this once the testing
+            // is done and this is no longer needed.
+            if (!hasQuarantineAppsPerm) {
+                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                        callingMethod);
+            }
+        } else {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                    callingMethod);
+        }
 
         final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId);
         final boolean allowedPackageUid = packageUid == callingUid;
@@ -5203,6 +5216,18 @@
         }
 
         @Override
+        public String getSuspendingPackage(String packageName, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshot();
+            // This will do visibility checks as well.
+            if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+                return null;
+            }
+            return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
+                    callingUid);
+        }
+
+        @Override
         public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() {
             // allow instant applications
             ArrayList<FeatureInfo> res;
@@ -6136,9 +6161,6 @@
                 PersistableBundle appExtras, PersistableBundle launcherExtras,
                 SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) {
             final int callingUid = Binder.getCallingUid();
-            final Computer snapshot = snapshotComputer();
-            enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
-                    "setPackagesSuspendedAsUser");
             boolean quarantined = false;
             if (Flags.quarantinedEnabled()) {
                 if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
@@ -6149,6 +6171,9 @@
                     quarantined = callingPackage.equals(wellbeingPkg);
                 }
             }
+            final Computer snapshot = snapshotComputer();
+            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid,
+                    userId, "setPackagesSuspendedAsUser");
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
                     appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
                     quarantined);
@@ -6963,6 +6988,11 @@
         }
 
         @Override
+        public PackageArchiver getPackageArchiver() {
+            return mInstallerService.mPackageArchiver;
+        }
+
+        @Override
         public void sendPackageRestartedBroadcast(@NonNull String packageName,
                 int uid, @Intent.Flags int flags) {
             final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6f45d2b..fc66203 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4689,10 +4689,12 @@
 
         final int translatedUserId =
                 translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
 
         try {
             mInterface.getPackageInstaller().requestUnarchive(packageName,
-                    /* callerPackageName= */ "", new UserHandle(translatedUserId));
+                    mContext.getPackageName(), receiver.getIntentSender(),
+                    new UserHandle(translatedUserId));
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 434a62d..15d2fdc 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -41,10 +41,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.InstallLocationUtils;
+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.pm.parsing.pkg.ParsedPackage;
 import com.android.server.rollback.RollbackManagerInternal;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3cf5481..293b873 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -44,9 +44,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -91,13 +91,15 @@
         @IntDef({
                 INSTALL_PERMISSION_FIXED,
                 UPDATE_AVAILABLE,
-                FORCE_QUERYABLE_OVERRIDE
+                FORCE_QUERYABLE_OVERRIDE,
+                SCANNED_AS_STOPPED_SYSTEM_APP
         })
         public @interface Flags {
         }
         private static final int INSTALL_PERMISSION_FIXED = 1;
         private static final int UPDATE_AVAILABLE = 1 << 1;
         private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
+        private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
     }
     private int mBooleans;
 
@@ -768,10 +770,19 @@
         onChanged();
     }
 
+    void setArchiveTimeMillis(long value, int userId) {
+        modifyUserState(userId).setArchiveTimeMillis(value);
+        onChanged();
+    }
+
     boolean getInstalled(int userId) {
         return readUserState(userId).isInstalled();
     }
 
+    boolean isArchived(int userId) {
+        return PackageArchiver.isArchived(readUserState(userId));
+    }
+
     int getInstallReason(int userId) {
         return readUserState(userId).getInstallReason();
     }
@@ -881,6 +892,12 @@
         onChanged();
     }
 
+    public PackageSetting setScannedAsStoppedSystemApp(boolean stop) {
+        setBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP, stop);
+        onChanged();
+        return this;
+    }
+
     boolean getNotLaunched(int userId) {
         return readUserState(userId).isNotLaunched();
     }
@@ -1559,6 +1576,11 @@
         return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0;
     }
 
+    @Override
+    public boolean isScannedAsStoppedSystemApp() {
+        return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1707,10 +1729,10 @@
     }
 
     @DataClass.Generated(
-            time = 1698188444364L,
+            time = 1700251133016L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\npublic  com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 5625884..1089ac9 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,9 +22,9 @@
 import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
 import java.util.concurrent.ArrayBlockingQueue;
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5312ae6..bb0017c 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -30,7 +30,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index a8196f3..80f69a4 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -395,11 +395,13 @@
                 mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
             }
             if (changedUsers.size() > 0) {
-                final PreferredActivityHelper preferredActivityHelper =
-                        new PreferredActivityHelper(mPm, mBroadcastHelper);
-                preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(),
-                        changedUsers);
-                mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+                mPm.mInjector.getBackgroundHandler().post(() -> {
+                    final PreferredActivityHelper preferredActivityHelper =
+                            new PreferredActivityHelper(mPm, mBroadcastHelper);
+                    preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(),
+                            changedUsers);
+                    mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+                });
             }
         } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
                 && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) {
@@ -409,12 +411,17 @@
             if (DEBUG_REMOVE) {
                 Slog.d(TAG, "Updating installed state to false because of DELETE_KEEP_DATA");
             }
+            final boolean isArchive = (flags & PackageManager.DELETE_ARCHIVE) != 0;
+            final long currentTimeMillis = System.currentTimeMillis();
             for (int userId : outInfo.mRemovedUsers) {
                 if (DEBUG_REMOVE) {
                     final boolean wasInstalled = deletedPs.getInstalled(userId);
                     Slog.d(TAG, "    user " + userId + ": " + wasInstalled + " => " + false);
                 }
                 deletedPs.setInstalled(/* installed= */ false, userId);
+                if (isArchive) {
+                    deletedPs.modifyUserState(userId).setArchiveTimeMillis(currentTimeMillis);
+                }
             }
         }
         // make sure to preserve per-user installed state if this removal was just
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 22ee963..53b84e6 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -73,6 +73,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.component.ParsedProcess;
@@ -83,7 +84,6 @@
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java
index e66a72f..37cf30b 100644
--- a/services/core/java/com/android/server/pm/ScanRequest.java
+++ b/services/core/java/com/android/server/pm/ScanRequest.java
@@ -21,7 +21,7 @@
 import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7c969ef..107dc76 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -373,6 +373,8 @@
     private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
     private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
 
+    private static final String ATTR_ARCHIVE_TIME = "archive-time";
+
     private final Handler mHandler;
 
     private final PackageManagerTracedLock mLock;
@@ -948,6 +950,7 @@
             ret.getPkgState().setUpdatedSystemApp(false);
             ret.setTargetSdkVersion(p.getTargetSdkVersion());
             ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
+            ret.setScannedAsStoppedSystemApp(p.isScannedAsStoppedSystemApp());
         }
         mDisabledSysPackages.remove(name);
         return ret;
@@ -1162,6 +1165,7 @@
                     Slog.i(PackageManagerService.TAG, "Stopping system package " + pkgName, e);
                 }
                 pkgSetting.setStopped(true, installUserId);
+                pkgSetting.setScannedAsStoppedSystemApp(true);
             }
             if (sharedUser != null) {
                 pkgSetting.setAppId(sharedUser.mAppId);
@@ -1929,6 +1933,8 @@
                                 ATTR_SPLASH_SCREEN_THEME);
                         final long firstInstallTime = parser.getAttributeLongHex(null,
                                 ATTR_FIRST_INSTALL_TIME, 0);
+                        final long archiveTime = parser.getAttributeLongHex(null,
+                                ATTR_ARCHIVE_TIME, 0);
                         final int minAspectRatio = parser.getAttributeInt(null,
                                 ATTR_MIN_ASPECT_RATIO,
                                 PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
@@ -2016,7 +2022,7 @@
                                 firstInstallTime != 0 ? firstInstallTime
                                         : origFirstInstallTimes.getOrDefault(name, 0L),
                                 minAspectRatio, archiveState);
-
+                        ps.setArchiveTimeMillis(archiveTime, userId);
                         mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                     } else if (tagName.equals("preferred-activities")) {
                         readPreferredActivitiesLPw(parser, userId);
@@ -2379,6 +2385,8 @@
                         }
                         serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME,
                                 ustate.getFirstInstallTimeMillis());
+                        serializer.attributeLongHex(null, ATTR_ARCHIVE_TIME,
+                                ustate.getArchiveTimeMillis());
                         if (ustate.getUninstallReason()
                                 != PackageManager.UNINSTALL_REASON_UNKNOWN) {
                             serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
@@ -3072,6 +3080,8 @@
             serializer.attributeBytesBase64(null, "restrictUpdateHash",
                     pkg.getRestrictUpdateHash());
         }
+        serializer.attributeBoolean(null, "scannedAsStoppedSystemApp",
+            pkg.isScannedAsStoppedSystemApp());
         if (pkg.getLegacyNativeLibraryPath() != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
         }
@@ -3140,6 +3150,8 @@
             serializer.attributeBytesBase64(null, "restrictUpdateHash",
                     pkg.getRestrictUpdateHash());
         }
+        serializer.attributeBoolean(null, "scannedAsStoppedSystemApp",
+            pkg.isScannedAsStoppedSystemApp());
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
         } else {
@@ -3873,6 +3885,8 @@
         int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
         byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
                 null);
+        boolean isScannedAsStoppedSystemApp =  parser.getAttributeBoolean(null,
+            "scannedAsStoppedSystemApp", false);
 
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
@@ -3893,7 +3907,8 @@
                 .setCpuAbiOverride(cpuAbiOverrideStr)
                 .setLongVersionCode(versionCode)
                 .setTargetSdkVersion(targetSdkVersion)
-                .setRestrictUpdateHash(restrictUpdateHash);
+                .setRestrictUpdateHash(restrictUpdateHash)
+                .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
         long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
         if (timeStamp == 0) {
             timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3988,6 +4003,7 @@
         String appMetadataFilePath = null;
         int targetSdkVersion = 0;
         byte[] restrictUpdateHash = null;
+        boolean isScannedAsStoppedSystemApp = false;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -4028,6 +4044,8 @@
             categoryHint = parser.getAttributeInt(null, "categoryHint",
                     ApplicationInfo.CATEGORY_UNDEFINED);
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
+            isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
+                "scannedAsStoppedSystemApp", false);
 
             String domainSetIdString = parser.getAttributeValue(null, "domainSetId");
 
@@ -4174,7 +4192,8 @@
                     .setLoadingCompletedTime(loadingCompletedTime)
                     .setAppMetadataFilePath(appMetadataFilePath)
                     .setTargetSdkVersion(targetSdkVersion)
-                    .setRestrictUpdateHash(restrictUpdateHash);
+                    .setRestrictUpdateHash(restrictUpdateHash)
+                    .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4970,6 +4989,10 @@
                 pw.print(prefix); pw.print("  privateFlags="); printFlags(pw,
                         privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println();
             }
+            if (!pkg.isUpdatableSystem()) {
+                pw.print(prefix); pw.print("  updatableSystem=false");
+                pw.println();
+            }
             if (pkg.hasPreserveLegacyExternalStorage()) {
                 pw.print(prefix); pw.print("  hasPreserveLegacyExternalStorage=true");
                 pw.println();
@@ -4988,6 +5011,8 @@
                 pw.append(prefix).append("  queriesIntents=")
                         .println(ps.getPkg().getQueriesIntents());
             }
+            pw.print(prefix); pw.print("  scannedAsStoppedSystemApp=");
+            pw.println(ps.isScannedAsStoppedSystemApp());
             pw.print(prefix); pw.print("  supportsScreens=[");
             boolean first = true;
             if (pkg.isSmallScreensSupported()) {
@@ -5268,6 +5293,10 @@
             date.setTime(pus.getFirstInstallTimeMillis());
             pw.println(sdf.format(date));
 
+            pw.print("      archiveTime=");
+            date.setTime(pus.getArchiveTimeMillis());
+            pw.println(sdf.format(date));
+
             pw.print("      uninstallReason=");
             pw.println(userState.getUninstallReason());
 
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 585e2e4..9384c13 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -46,11 +46,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.utils.Snappable;
@@ -856,9 +856,9 @@
         // We may not yet have disabled the updated package yet, so be sure to grab the
         // current setting if that's the case.
         final PackageSetting updatedSystemPs = isUpdatedSystemApp
-                ? installRequest.getDisabledPackageSetting() == null
+                ? installRequest.getScanRequestDisabledPackageSetting() == null
                 ? installRequest.getScanRequestOldPackageSetting()
-                : installRequest.getDisabledPackageSetting()
+                : installRequest.getScanRequestDisabledPackageSetting()
                 : null;
         if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
                 || updatedSystemPs.getPkg().getLibraryNames() == null)) {
diff --git a/services/core/java/com/android/server/pm/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
index 651578d..f120763 100644
--- a/services/core/java/com/android/server/pm/UserJourneyLogger.java
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -23,6 +23,7 @@
 import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
 
 import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
@@ -245,6 +246,9 @@
                         .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
             case USER_TYPE_PROFILE_CLONE:
                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
+            case USER_TYPE_PROFILE_PRIVATE:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_PRIVATE;
             default:
                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4e14c90..f90bf4b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1592,6 +1592,19 @@
      */
     private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userId, @Nullable IntentSender target) {
+        if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
+            // TODO (b/308121702) It may be brittle to rely on user states to check profile state
+            int state;
+            synchronized (mUserStates) {
+                state = mUserStates.get(userId, UserState.STATE_NONE);
+            }
+            if (state != UserState.STATE_NONE) {
+                Slog.i(LOG_TAG,
+                        "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
+                                + " is still alive.");
+                return;
+            }
+        }
         // otherwise, we show a profile challenge to trigger decryption of the user
         final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
                 Context.KEYGUARD_SERVICE);
@@ -1944,6 +1957,19 @@
         return userTypeDetails.getStatusBarIcon();
     }
 
+    @Override
+    public @StringRes int getProfileLabelResId(@UserIdInt int userId) {
+        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "getProfileLabelResId");
+        final UserInfo userInfo = getUserInfoNoChecks(userId);
+        final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
+        if (userInfo == null || userTypeDetails == null) {
+            return Resources.ID_NULL;
+        }
+        final int userIndex = userInfo.profileBadge;
+        return userTypeDetails.getLabel(userIndex);
+    }
+
     public boolean isProfile(@UserIdInt int userId) {
         checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
         return isProfileUnchecked(userId);
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 7bdcd68..56c400a0 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -54,8 +54,15 @@
     /** Whether users of this type can be created. */
     private final boolean mEnabled;
 
-    // TODO(b/142482943): Currently unused and not set. Hook this up.
-    private final int mLabel;
+    /**
+     * Resource IDs ({@link StringRes}) of the user's labels. This might be used to label a
+     * user/profile in tabbed views, etc.
+     * The values are resource IDs referring to the strings not the strings themselves.
+     *
+     * <p>This is an array because, in general, there may be multiple users of the same user type.
+     * In this case, the user is indexed according to its {@link UserInfo#profileBadge}.
+     */
+    private final @Nullable int[] mLabels;
 
     /**
      * Maximum number of this user type allowed on the device.
@@ -160,8 +167,8 @@
     private final @NonNull UserProperties mDefaultUserProperties;
 
     private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
-            @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
-            int maxAllowedPerParent,
+            @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags,
+            @Nullable int[] labels, int maxAllowedPerParent,
             int iconBadge, int badgePlain, int badgeNoBackground,
             int statusBarIcon,
             @Nullable int[] badgeLabels, @Nullable int[] badgeColors,
@@ -181,12 +188,11 @@
         this.mDefaultSystemSettings = defaultSystemSettings;
         this.mDefaultSecureSettings = defaultSecureSettings;
         this.mDefaultCrossProfileIntentFilters = defaultCrossProfileIntentFilters;
-
         this.mIconBadge = iconBadge;
         this.mBadgePlain = badgePlain;
         this.mBadgeNoBackground = badgeNoBackground;
         this.mStatusBarIcon = statusBarIcon;
-        this.mLabel = label;
+        this.mLabels = labels;
         this.mBadgeLabels = badgeLabels;
         this.mBadgeColors = badgeColors;
         this.mDarkThemeBadgeColors = darkThemeBadgeColors;
@@ -234,9 +240,16 @@
         return mDefaultUserInfoPropertyFlags | mBaseType;
     }
 
-    // TODO(b/142482943) Hook this up; it is currently unused.
-    public int getLabel() {
-        return mLabel;
+    /**
+     * Returns the resource ID corresponding to the badgeIndexth label name where the badgeIndex is
+     * expected to be the {@link UserInfo#profileBadge} of the user. If badgeIndex exceeds the
+     * number of labels, returns the label for the highest index.
+     */
+    public @StringRes int getLabel(int badgeIndex) {
+        if (mLabels == null || mLabels.length == 0 || badgeIndex < 0) {
+            return Resources.ID_NULL;
+        }
+        return mLabels[Math.min(badgeIndex, mLabels.length - 1)];
     }
 
     /** Returns whether users of this user type should be badged. */
@@ -358,7 +371,6 @@
         pw.print(prefix); pw.print("mMaxAllowedPerParent: "); pw.println(mMaxAllowedPerParent);
         pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
         pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
-        pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
         mDefaultUserProperties.println(pw, prefix);
 
         final String restrictionsPrefix = prefix + "    ";
@@ -392,6 +404,8 @@
         pw.println(mBadgeColors != null ? mBadgeColors.length : "0(null)");
         pw.print(prefix); pw.print("mDarkThemeBadgeColors.length: ");
         pw.println(mDarkThemeBadgeColors != null ? mDarkThemeBadgeColors.length : "0(null)");
+        pw.print(prefix); pw.print("mLabels.length: ");
+        pw.println(mLabels != null ? mLabels.length : "0(null)");
     }
 
     /** Builder for a {@link UserTypeDetails}; see that class for documentation. */
@@ -408,7 +422,7 @@
         private @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters =
                 null;
         private int mEnabled = 1;
-        private int mLabel = Resources.ID_NULL;
+        private @Nullable int[] mLabels = null;
         private @Nullable int[] mBadgeLabels = null;
         private @Nullable int[] mBadgeColors = null;
         private @Nullable int[] mDarkThemeBadgeColors = null;
@@ -488,8 +502,8 @@
             return this;
         }
 
-        public Builder setLabel(int label) {
-            mLabel = label;
+        public Builder setLabels(@StringRes int ... labels) {
+            mLabels = labels;
             return this;
         }
 
@@ -562,7 +576,7 @@
                     mMaxAllowed,
                     mBaseType,
                     mDefaultUserInfoPropertyFlags,
-                    mLabel,
+                    mLabels,
                     mMaxAllowedPerParent,
                     mIconBadge,
                     mBadgePlain,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7da76c1..4ef8cb7 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,7 @@
                 .setName(USER_TYPE_PROFILE_CLONE)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
-                .setLabel(0)
+                .setLabels(R.string.profile_label_clone)
                 .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
                 .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
                 // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
@@ -154,6 +154,10 @@
                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
                         .setCrossProfileIntentResolutionStrategy(UserProperties
                                 .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING)
+                        .setShowInQuietMode(
+                                UserProperties.SHOW_IN_QUIET_MODE_DEFAULT)
+                        .setShowInSharingSurfaces(
+                                UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT)
                         .setMediaSharedWithParent(true)
                         .setCredentialShareableWithParent(true)
                         .setDeleteAppWithParent(true));
@@ -169,7 +173,10 @@
                 .setBaseType(FLAG_PROFILE)
                 .setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE)
                 .setMaxAllowedPerParent(1)
-                .setLabel(0)
+                .setLabels(
+                        R.string.profile_label_work,
+                        R.string.profile_label_work_2,
+                        R.string.profile_label_work_3)
                 .setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case)
                 .setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case)
                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background)
@@ -193,6 +200,10 @@
                         .setStartWithParent(true)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setShowInQuietMode(
+                                UserProperties.SHOW_IN_QUIET_MODE_PAUSED)
+                        .setShowInSharingSurfaces(
+                                UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
                         .setAuthAlwaysRequiredToDisableQuietMode(false)
                         .setCredentialShareableWithParent(true));
     }
@@ -209,7 +220,10 @@
                 .setName(USER_TYPE_PROFILE_TEST)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(2)
-                .setLabel(0)
+                .setLabels(
+                        R.string.profile_label_test,
+                        R.string.profile_label_test,
+                        R.string.profile_label_test)
                 .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
                 .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
@@ -240,7 +254,7 @@
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowed(1)
                 .setEnabled(UserManager.isCommunalProfileEnabled() ? 1 : 0)
-                .setLabel(0)
+                .setLabels(R.string.profile_label_communal)
                 .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
                 .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
@@ -276,7 +290,7 @@
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
-                .setLabel(0)
+                .setLabels(R.string.profile_label_private)
                 .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
                 .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
                 // Private Profile doesn't use BadgeNoBackground, so just set to BadgePlain
@@ -298,7 +312,10 @@
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
-                        .setHideInSettingsInQuietMode(true)
+                        .setShowInQuietMode(
+                                UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+                        .setShowInSharingSurfaces(
+                                UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
                         .setCrossProfileIntentFilterAccessControl(
                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
                         .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 2ab7db4..459e2cf 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,9 +28,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 91a70a6..7910edc 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -152,6 +152,7 @@
         info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
         info.firstInstallTime = firstInstallTime;
         info.lastUpdateTime = lastUpdateTime;
+        info.setArchiveTimeMillis(state.getArchiveTimeMillis());
         if ((flags & PackageManager.GET_GIDS) != 0) {
             info.gids = gids;
         }
@@ -410,6 +411,9 @@
             ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]);
         }
         ai.isArchived = PackageArchiver.isArchived(state);
+        if (ai.isArchived) {
+            ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle();
+        }
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 3b10c7f..1c751e0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -35,12 +35,12 @@
 import android.util.Slog;
 
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
index 8b5719a..f4e187f 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
@@ -21,7 +21,7 @@
 import android.os.Build;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 
 /**
  * Updates a package to ensure that if it targets <= P that the android.hidl.base-V1.0-java
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
index adaa04c..34880a8d 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
@@ -16,7 +16,7 @@
 package com.android.server.pm.parsing.library;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 
 /**
  * Updates a package to remove dependency on android.net.ipsec.ike library.
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
index 3b29d1f..97e3020 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
@@ -29,8 +29,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 /**
diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
index 041b77b..0c38c60 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
@@ -19,9 +19,9 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.modules.utils.build.UnboundedSdkLevel;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 /**
  * Updates packages to add or remove dependencies on shared libraries as per attributes
diff --git a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
index b47a768..1c6c1fb 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
@@ -16,7 +16,7 @@
 package com.android.server.pm.parsing.library;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 
 /**
  * Updates a package to remove dependency on com.google.android.maps library.
diff --git a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
index ac65c8c..b558981 100644
--- a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
@@ -20,7 +20,7 @@
 import android.os.Build;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 /**
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index 3da7141..fe9cd0e 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -24,9 +24,9 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
index a9c22d9..a5af005 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
@@ -19,8 +19,8 @@
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 0eb2bbd..61be6e1 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -29,17 +29,18 @@
 import android.os.incremental.IncrementalManager;
 
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
 
 import java.io.IOException;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 370d239..85d95ea 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -50,6 +50,10 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.AndroidPackageSplitImpl;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -63,6 +67,8 @@
 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.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
@@ -71,7 +77,6 @@
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.AndroidPackageSplit;
-import com.android.server.pm.pkg.AndroidPackageSplitImpl;
 import com.android.server.pm.pkg.SELinuxUtil;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
@@ -84,8 +89,6 @@
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
@@ -397,7 +400,7 @@
     // an APK targeting <R that doesn't contain an <application> tag. That code would be skipped
     // and never assign this, so initialize this to true for those cases.
     private long mBooleans = Booleans.ENABLED;
-    private long mBooleans2;
+    private long mBooleans2 = Booleans2.UPDATABLE_SYSTEM;
     @NonNull
     private Set<String> mKnownActivityEmbeddingCerts = emptySet();
     // Derived fields
@@ -3451,6 +3454,11 @@
     }
 
     @Override
+    public boolean isUpdatableSystem() {
+        return getBoolean2(Booleans2.UPDATABLE_SYSTEM);
+    }
+
+    @Override
     public boolean isFactoryTest() {
         return getBoolean(Booleans.FACTORY_TEST);
     }
@@ -3522,6 +3530,11 @@
     }
 
     @Override
+    public PackageImpl setUpdatableSystem(boolean value) {
+        return setBoolean2(Booleans2.UPDATABLE_SYSTEM, value);
+    }
+
+    @Override
     public PackageImpl setFactoryTest(boolean value) {
         setBoolean(Booleans.FACTORY_TEST, value);
         return this;
@@ -3732,10 +3745,12 @@
         @LongDef({
                 STUB,
                 APEX,
+                UPDATABLE_SYSTEM,
         })
         public @interface Flags {}
 
         private static final long STUB = 1L;
         private static final long APEX = 1L << 1;
+        private static final long UPDATABLE_SYSTEM = 1L << 2;
     }
 }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6f6bb45..f7f76aa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -807,7 +807,7 @@
                     getDefaultSystemHandlerActivityPackage(pm,
                             SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
                     userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
-                    NOTIFICATION_PERMISSIONS);
+                    NOTIFICATION_PERMISSIONS, PHONE_PERMISSIONS);
         }
 
         // Voice recognition
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 883b066..8bd2d94 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -70,6 +70,7 @@
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
@@ -5327,7 +5328,8 @@
                     IOnPermissionsChangeListener callback = mPermissionListeners
                             .getBroadcastItem(i);
                     try {
-                        callback.onPermissionsChanged(uid);
+                        callback.onPermissionsChanged(uid,
+                                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Permission listener is dead", e);
                     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e7137bb..10b59c7 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -456,4 +456,12 @@
     @Immutable.Ignore
     @Nullable
     byte[] getRestrictUpdateHash();
+
+    /**
+     * whether the package has been scanned as a stopped system app. A package will be
+     * scanned in the stopped state if it is a system app that has a launcher entry and is
+     * <b>not</b> exempted by {@code <initial-package-state>} tag, and is not an APEX
+     * @hide
+     */
+    boolean isScannedAsStoppedSystemApp();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index fc74a195..c737283 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -22,9 +22,9 @@
 import android.content.pm.SigningDetails;
 import android.util.SparseArray;
 
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.server.pm.InstallSource;
 import com.android.server.pm.PackageKeySetData;
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.server.pm.permission.LegacyPermissionState;
 
 import java.util.UUID;
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index 2a81a86..8eb3466 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -256,4 +256,10 @@
      * @hide
      */
     boolean dataExists();
+
+    /**
+     * Timestamp of when the app is archived on the user.
+     * @hide
+     */
+    long getArchiveTimeMillis();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 2f4ad2d8..defd343 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -206,4 +206,9 @@
     public boolean dataExists() {
         return true;
     }
+
+    @Override
+    public long getArchiveTimeMillis() {
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index a76a7ce0..c0ea7cc 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -135,6 +135,8 @@
     @Nullable
     private ArchiveState mArchiveState;
 
+    private @CurrentTimeMillisLong long mArchiveTimeMillis;
+
     @NonNull
     final SnapshotCache<PackageUserStateImpl> mSnapshot;
 
@@ -187,6 +189,7 @@
                 ? null : other.mComponentLabelIconOverrideMap.snapshot();
         mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
         mArchiveState = other.mArchiveState;
+        mArchiveTimeMillis = other.mArchiveTimeMillis;
         mSnapshot = new SnapshotCache.Sealed<>();
     }
 
@@ -602,8 +605,6 @@
 
     /**
      * Sets the value for {@link #getArchiveState()}.
-     *
-     * @hide
      */
     @NonNull
     public PackageUserStateImpl setArchiveState(@NonNull ArchiveState archiveState) {
@@ -612,6 +613,16 @@
         return this;
     }
 
+    /**
+     * Sets the timestamp when the app is archived on this user.
+     */
+    @NonNull
+    public PackageUserStateImpl setArchiveTimeMillis(@CurrentTimeMillisLong long value) {
+        mArchiveTimeMillis = value;
+        onChanged();
+        return this;
+    }
+
     @NonNull
     @Override
     public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
@@ -800,6 +811,11 @@
     }
 
     @DataClass.Generated.Member
+    public @CurrentTimeMillisLong long getArchiveTimeMillis() {
+        return mArchiveTimeMillis;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
         return mSnapshot;
     }
@@ -876,6 +892,7 @@
                 && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
                 && watchableEquals(that.mWatchable)
                 && Objects.equals(mArchiveState, that.mArchiveState)
+                && mArchiveTimeMillis == that.mArchiveTimeMillis
                 && snapshotEquals(that.mSnapshot);
     }
 
@@ -906,15 +923,16 @@
         _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
         _hash = 31 * _hash + watchableHashCode();
         _hash = 31 * _hash + Objects.hashCode(mArchiveState);
+        _hash = 31 * _hash + Long.hashCode(mArchiveTimeMillis);
         _hash = 31 * _hash + snapshotHashCode();
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1694196888631L,
+            time = 1699917927942L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nprivate @android.annotation.CurrentTimeMillisLong long mArchiveTimeMillis\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
index 041edaa..019ca13 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
@@ -32,9 +32,9 @@
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java
index 1497684..dd54cfc 100644
--- a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java
@@ -26,8 +26,8 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.SystemConfig;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index 5709cbb..64985bd 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -49,8 +49,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
index c6b9b1a..9322cf0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
@@ -31,7 +31,7 @@
 import android.util.TypedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
index 9792a91..a711694 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -27,7 +27,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
index 5e67bbf..e5e214d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -32,7 +32,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
index 6c22f82..8268f0f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
@@ -33,7 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
index 0f2b49b..4b45d37 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
@@ -33,7 +33,7 @@
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
index 766fb90..a849549 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
@@ -28,9 +28,9 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index b66db4f..0b28a12 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -35,7 +35,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index 1b42184..171ef59 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -33,7 +33,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 417e3ae..722350a 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -89,6 +89,7 @@
 
 import com.android.internal.R;
 import com.android.internal.os.ClassLoaderFactory;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -102,11 +103,11 @@
 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.ParsingPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.SharedUidMigration;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.CompatibilityPermissionInfo;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ComponentParseUtils;
@@ -1002,12 +1003,16 @@
             return sharedUserResult;
         }
 
+        final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
+                "updatableSystem", true);
+
         pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                         R.styleable.AndroidManifest_installLocation, sa))
                 .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
-                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
+                .setUpdatableSystem(updatableSystem);
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 2cfffb3..1d15955 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -31,6 +31,7 @@
 import android.util.Slog;
 
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index ed6d3b9..532a7f8 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -943,6 +943,12 @@
                 return false;
             }
 
+            if (packageState.isSystem()) {
+                // A system app can be considered in the stopped state only if it was originally
+                // scanned in the stopped state.
+                return packageState.isScannedAsStoppedSystemApp() &&
+                    packageState.getUserStateOrDefault(userId).isStopped();
+            }
             return packageState.getUserStateOrDefault(userId).isStopped();
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 72c10cc..73c4224 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -202,6 +202,7 @@
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
+import com.android.internal.display.BrightnessUtils;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -221,7 +222,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
-import com.android.server.display.BrightnessUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
@@ -1073,7 +1073,7 @@
         }
     }
 
-    private void powerPress(long eventTime, int count) {
+    private void powerPress(long eventTime, int count, int displayId) {
         // SideFPS still needs to know about suppressed power buttons, in case it needs to block
         // an auth attempt.
         if (count == 1) {
@@ -1126,8 +1126,9 @@
                     break;
                 case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
                     if (mDismissImeOnBackKeyPressed) {
-                        InputMethodManagerInternal.get().hideCurrentInputMethod(
-                                    SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
+                        // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
+                        InputMethodManagerInternal.get().hideAllInputMethods(
+                                SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, displayId);
                     } else {
                         shortPressPowerGoHome();
                     }
@@ -2662,11 +2663,11 @@
         }
 
         @Override
-        void onPress(long downTime) {
+        void onPress(long downTime, int displayId) {
             if (mShouldEarlyShortPressOnPower) {
                 return;
             }
-            powerPress(downTime, 1 /*count*/);
+            powerPress(downTime, 1 /*count*/, displayId);
         }
 
         @Override
@@ -2696,14 +2697,14 @@
         }
 
         @Override
-        void onMultiPress(long downTime, int count) {
-            powerPress(downTime, count);
+        void onMultiPress(long downTime, int count, int displayId) {
+            powerPress(downTime, count, displayId);
         }
 
         @Override
-        void onKeyUp(long eventTime, int count) {
+        void onKeyUp(long eventTime, int count, int displayId) {
             if (mShouldEarlyShortPressOnPower && count == 1) {
-                powerPress(eventTime, 1 /*pressCount*/);
+                powerPress(eventTime, 1 /*pressCount*/, displayId);
             }
         }
     }
@@ -2727,7 +2728,7 @@
         }
 
         @Override
-        void onPress(long downTime) {
+        void onPress(long downTime, int unusedDisplayId) {
             mBackKeyHandled |= backKeyPress();
         }
 
@@ -2756,7 +2757,7 @@
         }
 
         @Override
-        void onPress(long downTime) {
+        void onPress(long downTime, int unusedDisplayId) {
             if (mShouldEarlyShortPressOnStemPrimary) {
                 return;
             }
@@ -2769,12 +2770,12 @@
         }
 
         @Override
-        void onMultiPress(long downTime, int count) {
+        void onMultiPress(long downTime, int count, int unusedDisplayId) {
             stemPrimaryPress(count);
         }
 
         @Override
-        void onKeyUp(long eventTime, int count) {
+        void onKeyUp(long eventTime, int count, int unusedDisplayId) {
             if (count == 1) {
                 // Save info about the most recent task on the first press of the stem key. This
                 // may be used later to switch to the most recent app using double press gesture.
@@ -3536,7 +3537,8 @@
                     mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
 
                     Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION
+                            | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
                     logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 047555a..a060f50 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -66,10 +66,12 @@
      *  SingleKeyRule rule =
      *      new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) {
      *           int getMaxMultiPressCount() { // maximum multi press count. }
-     *           void onPress(long downTime) { // short press behavior. }
+     *           void onPress(long downTime, int displayId) { // short press behavior. }
      *           void onLongPress(long eventTime) { // long press behavior. }
      *           void onVeryLongPress(long eventTime) { // very long press behavior. }
-     *           void onMultiPress(long downTime, int count) { // multi press behavior.  }
+     *           void onMultiPress(long downTime, int count, int displayId) {
+     *               // multi press behavior.
+     *           }
      *       };
      *  </pre>
      */
@@ -114,11 +116,11 @@
         /**
          *  Called when short press has been detected.
          */
-        abstract void onPress(long downTime);
+        abstract void onPress(long downTime, int displayId);
         /**
          *  Callback when multi press (>= 2) has been detected.
          */
-        void onMultiPress(long downTime, int count) {}
+        void onMultiPress(long downTime, int count, int displayId) {}
         /**
          *  Returns the timeout in milliseconds for a long press.
          *
@@ -148,10 +150,11 @@
         /**
          * Callback executed upon each key up event that hasn't been processed by long press.
          *
-         * @param eventTime the timestamp of this event.
-         * @param pressCount the number of presses detected leading up to this key up event.
+         * @param eventTime  the timestamp of this event
+         * @param pressCount the number of presses detected leading up to this key up event
+         * @param displayId  the display ID of the event
          */
-        void onKeyUp(long eventTime, int pressCount) {}
+        void onKeyUp(long eventTime, int pressCount, int displayId) {}
 
         @Override
         public String toString() {
@@ -179,6 +182,10 @@
         }
     }
 
+    private record MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount,
+                                 int displayId) {
+    }
+
     static SingleKeyGestureDetector get(Context context, Looper looper) {
         SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper);
         sDefaultLongPressTimeout = context.getResources().getInteger(
@@ -228,8 +235,9 @@
                 mHandledByLongPress = true;
                 mHandler.removeMessages(MSG_KEY_LONG_PRESS);
                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
-                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
-                        mActiveRule);
+                MessageObject object = new MessageObject(mActiveRule, keyCode, /* pressCount= */ 1,
+                        event.getDisplayId());
+                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessage(msg);
             }
@@ -275,15 +283,17 @@
 
         if (mKeyPressCounter == 1) {
             if (mActiveRule.supportLongPress()) {
-                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
-                        mActiveRule);
+                MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter,
+                        event.getDisplayId());
+                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
             }
 
             if (mActiveRule.supportVeryLongPress()) {
-                final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
-                        mActiveRule);
+                MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter,
+                        event.getDisplayId());
+                final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs());
             }
@@ -299,8 +309,9 @@
                     Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
                             + " reached the max count " + mKeyPressCounter);
                 }
-                final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, keyCode,
-                        mKeyPressCounter, mActiveRule);
+                MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter,
+                        event.getDisplayId());
+                final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessage(msg);
             }
@@ -339,9 +350,9 @@
 
         if (event.getKeyCode() == mActiveRule.mKeyCode) {
             // key-up action should always be triggered if not processed by long press.
-            Message msgKeyUp =
-                    mHandler.obtainMessage(
-                            MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule);
+            MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode,
+                    mKeyPressCounter, event.getDisplayId());
+            Message msgKeyUp = mHandler.obtainMessage(MSG_KEY_UP, object);
             msgKeyUp.setAsynchronous(true);
             mHandler.sendMessage(msgKeyUp);
 
@@ -350,8 +361,9 @@
                 if (DEBUG) {
                     Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode()));
                 }
-                Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
-                        1, mActiveRule);
+                object = new MessageObject(mActiveRule, mActiveRule.mKeyCode,
+                        /* pressCount= */ 1, event.getDisplayId());
+                Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessage(msg);
                 mActiveRule = null;
@@ -360,8 +372,9 @@
 
             // This could be a multi-press.  Wait a little bit longer to confirm.
             if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) {
-                Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
-                        mKeyPressCounter, mActiveRule);
+                object = new MessageObject(mActiveRule, mActiveRule.mKeyCode,
+                        mKeyPressCounter, event.getDisplayId());
+                Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
             }
@@ -423,20 +436,23 @@
 
         @Override
         public void handleMessage(Message msg) {
-            final SingleKeyRule rule = (SingleKeyRule) msg.obj;
+            final MessageObject object = (MessageObject) msg.obj;
+            final SingleKeyRule rule = object.activeRule;
             if (rule == null) {
                 Log.wtf(TAG, "No active rule.");
                 return;
             }
 
-            final int keyCode = msg.arg1;
-            final int pressCount = msg.arg2;
+            final int keyCode = object.keyCode;
+            final int pressCount = object.pressCount;
+            final int displayId = object.displayId;
             switch(msg.what) {
                 case MSG_KEY_UP:
                     if (DEBUG) {
-                        Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode));
+                        Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode)
+                                + " on display " + displayId);
                     }
-                    rule.onKeyUp(mLastDownTime, pressCount);
+                    rule.onKeyUp(mLastDownTime, pressCount, displayId);
                     break;
                 case MSG_KEY_LONG_PRESS:
                     if (DEBUG) {
@@ -454,12 +470,12 @@
                 case MSG_KEY_DELAYED_PRESS:
                     if (DEBUG) {
                         Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode)
-                                + ", count " + pressCount);
+                                + " on display " + displayId + ", count " + pressCount);
                     }
                     if (pressCount == 1) {
-                        rule.onPress(mLastDownTime);
+                        rule.onPress(mLastDownTime, displayId);
                     } else {
-                        rule.onMultiPress(mLastDownTime, pressCount);
+                        rule.onMultiPress(mLastDownTime, pressCount, displayId);
                     }
                     break;
             }
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 1970ee4..94340ec 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,6 +2,6 @@
 santoscordon@google.com
 philipjunker@google.com
 
-per-file ThermalManagerService.java=wvw@google.com
+per-file ThermalManagerService.java=file:/THERMAL_OWNERS
 per-file LowPowerStandbyController.java=qingxun@google.com
-per-file LowPowerStandbyControllerInternal.java=qingxun@google.com
\ No newline at end of file
+per-file LowPowerStandbyControllerInternal.java=qingxun@google.com
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index e3f3638..5d90851 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -32,7 +32,6 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 
@@ -41,6 +40,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ConcurrentUtils;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -809,12 +809,13 @@
 
         private static Policy fromSettings(String settings, String deviceSpecificSettings,
                 DeviceConfig.Properties properties, String configSuffix, Policy defaultPolicy) {
-            final KeyValueListParser parser = new KeyValueListParser(',');
+            final UserSettingDeviceConfigMediator userSettingDeviceConfigMediator =
+                    new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
             configSuffix = TextUtils.emptyIfNull(configSuffix);
 
             // Device-specific parameters.
             try {
-                parser.setString(deviceSpecificSettings == null ? "" : deviceSpecificSettings);
+                userSettingDeviceConfigMediator.setSettingsString(deviceSpecificSettings);
             } catch (IllegalArgumentException e) {
                 Slog.wtf(TAG, "Bad device specific battery saver constants: "
                         + deviceSpecificSettings);
@@ -822,68 +823,58 @@
 
             // Non-device-specific parameters.
             try {
-                parser.setString(settings == null ? "" : settings);
+                userSettingDeviceConfigMediator.setSettingsString(settings);
+                userSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
             } catch (IllegalArgumentException e) {
                 Slog.wtf(TAG, "Bad battery saver constants: " + settings);
             }
 
             // The Settings value overrides everything, since that will be set by the user.
             // The DeviceConfig value takes second place, with the default as the last choice.
-            final float adjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR,
-                    properties.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix,
-                            defaultPolicy.adjustBrightnessFactor));
-            final boolean advertiseIsEnabled = parser.getBoolean(KEY_ADVERTISE_IS_ENABLED,
-                    properties.getBoolean(KEY_ADVERTISE_IS_ENABLED + configSuffix,
-                            defaultPolicy.advertiseIsEnabled));
-            final boolean deferFullBackup = parser.getBoolean(KEY_DEFER_FULL_BACKUP,
-                    properties.getBoolean(KEY_DEFER_FULL_BACKUP + configSuffix,
-                            defaultPolicy.deferFullBackup));
-            final boolean deferKeyValueBackup = parser.getBoolean(KEY_DEFER_KEYVALUE_BACKUP,
-                    properties.getBoolean(KEY_DEFER_KEYVALUE_BACKUP + configSuffix,
-                            defaultPolicy.deferKeyValueBackup));
-            final boolean disableAnimation = parser.getBoolean(KEY_DISABLE_ANIMATION,
-                    properties.getBoolean(KEY_DISABLE_ANIMATION + configSuffix,
-                            defaultPolicy.disableAnimation));
-            final boolean disableAod = parser.getBoolean(KEY_DISABLE_AOD,
-                    properties.getBoolean(KEY_DISABLE_AOD + configSuffix,
-                            defaultPolicy.disableAod));
-            final boolean disableLaunchBoost = parser.getBoolean(KEY_DISABLE_LAUNCH_BOOST,
-                    properties.getBoolean(KEY_DISABLE_LAUNCH_BOOST + configSuffix,
-                            defaultPolicy.disableLaunchBoost));
-            final boolean disableOptionalSensors = parser.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS,
-                    properties.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS + configSuffix,
-                            defaultPolicy.disableOptionalSensors));
-            final boolean disableVibrationConfig = parser.getBoolean(KEY_DISABLE_VIBRATION,
-                    properties.getBoolean(KEY_DISABLE_VIBRATION + configSuffix,
-                            defaultPolicy.disableVibration));
-            final boolean enableBrightnessAdjustment = parser.getBoolean(
-                    KEY_ENABLE_BRIGHTNESS_ADJUSTMENT,
-                    properties.getBoolean(KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix,
-                            defaultPolicy.enableAdjustBrightness));
-            final boolean enableDataSaver = parser.getBoolean(KEY_ENABLE_DATASAVER,
-                    properties.getBoolean(KEY_ENABLE_DATASAVER + configSuffix,
-                            defaultPolicy.enableDataSaver));
-            final boolean enableFirewall = parser.getBoolean(KEY_ENABLE_FIREWALL,
-                    properties.getBoolean(KEY_ENABLE_FIREWALL + configSuffix,
-                            defaultPolicy.enableFirewall));
-            final boolean enableNightMode = parser.getBoolean(KEY_ENABLE_NIGHT_MODE,
-                    properties.getBoolean(KEY_ENABLE_NIGHT_MODE + configSuffix,
-                            defaultPolicy.enableNightMode));
-            final boolean enableQuickDoze = parser.getBoolean(KEY_ENABLE_QUICK_DOZE,
-                    properties.getBoolean(KEY_ENABLE_QUICK_DOZE + configSuffix,
-                            defaultPolicy.enableQuickDoze));
-            final boolean forceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY,
-                    properties.getBoolean(KEY_FORCE_ALL_APPS_STANDBY + configSuffix,
-                            defaultPolicy.forceAllAppsStandby));
-            final boolean forceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK,
-                    properties.getBoolean(KEY_FORCE_BACKGROUND_CHECK + configSuffix,
-                            defaultPolicy.forceBackgroundCheck));
-            final int locationMode = parser.getInt(KEY_LOCATION_MODE,
-                    properties.getInt(KEY_LOCATION_MODE + configSuffix,
-                            defaultPolicy.locationMode));
-            final int soundTriggerMode = parser.getInt(KEY_SOUNDTRIGGER_MODE,
-                    properties.getInt(KEY_SOUNDTRIGGER_MODE + configSuffix,
-                            defaultPolicy.soundTriggerMode));
+            final float adjustBrightnessFactor = userSettingDeviceConfigMediator.getFloat(
+                    KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix,
+                    defaultPolicy.adjustBrightnessFactor);
+            final boolean advertiseIsEnabled = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ADVERTISE_IS_ENABLED + configSuffix,
+                    defaultPolicy.advertiseIsEnabled);
+            final boolean deferFullBackup = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DEFER_FULL_BACKUP + configSuffix, defaultPolicy.deferFullBackup);
+            final boolean deferKeyValueBackup = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DEFER_KEYVALUE_BACKUP + configSuffix,
+                    defaultPolicy.deferKeyValueBackup);
+            final boolean disableAnimation = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_ANIMATION + configSuffix, defaultPolicy.disableAnimation);
+            final boolean disableAod = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_AOD + configSuffix, defaultPolicy.disableAod);
+            final boolean disableLaunchBoost = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_LAUNCH_BOOST + configSuffix,
+                    defaultPolicy.disableLaunchBoost);
+            final boolean disableOptionalSensors = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_OPTIONAL_SENSORS + configSuffix,
+                    defaultPolicy.disableOptionalSensors);
+            final boolean disableVibrationConfig = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_VIBRATION + configSuffix, defaultPolicy.disableVibration);
+            final boolean enableBrightnessAdjustment = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix,
+                    defaultPolicy.enableAdjustBrightness);
+            final boolean enableDataSaver = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_DATASAVER + configSuffix, defaultPolicy.enableDataSaver);
+            final boolean enableFirewall = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_FIREWALL + configSuffix, defaultPolicy.enableFirewall);
+            final boolean enableNightMode = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_NIGHT_MODE + configSuffix, defaultPolicy.enableNightMode);
+            final boolean enableQuickDoze = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_QUICK_DOZE + configSuffix, defaultPolicy.enableQuickDoze);
+            final boolean forceAllAppsStandby = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_FORCE_ALL_APPS_STANDBY + configSuffix,
+                    defaultPolicy.forceAllAppsStandby);
+            final boolean forceBackgroundCheck = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_FORCE_BACKGROUND_CHECK + configSuffix,
+                    defaultPolicy.forceBackgroundCheck);
+            final int locationMode = userSettingDeviceConfigMediator.getInt(
+                    KEY_LOCATION_MODE + configSuffix, defaultPolicy.locationMode);
+            final int soundTriggerMode = userSettingDeviceConfigMediator.getInt(
+                    KEY_SOUNDTRIGGER_MODE + configSuffix, defaultPolicy.soundTriggerMode);
             return new Policy(
                     adjustBrightnessFactor,
                     advertiseIsEnabled,
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 ee3b746..dd39fb0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -32,8 +32,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.os.WorkDuration;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseIntArray;
@@ -197,9 +195,6 @@
 
         private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
 
-        private static native void nativeReportActualWorkDuration(long halPtr,
-                                                                  WorkDuration[] workDurations);
-
         /** Wrapper for HintManager.nativeInit */
         public void halInit() {
             nativeInit();
@@ -257,10 +252,6 @@
             nativeSetMode(halPtr, mode, enabled);
         }
 
-        /** Wrapper for HintManager.nativeReportActualWorkDuration */
-        public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
-            nativeReportActualWorkDuration(halPtr, workDurations);
-        }
     }
 
     @VisibleForTesting
@@ -633,52 +624,6 @@
             }
         }
 
-        @Override
-        public void reportActualWorkDuration2(WorkDuration[] workDurations) {
-            synchronized (this) {
-                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
-                    return;
-                }
-                Preconditions.checkArgument(workDurations.length != 0, "the count"
-                        + " of work durations shouldn't be 0.");
-                for (WorkDuration workDuration : workDurations) {
-                    validateWorkDuration(workDuration);
-                }
-                mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
-            }
-        }
-
-        void validateWorkDuration(WorkDuration workDuration) {
-            if (DEBUG) {
-                Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
-                        + workDuration.getWorkPeriodStartTimestampNanos() + ", "
-                        + workDuration.getActualTotalDurationNanos() + ", "
-                        + workDuration.getActualCpuDurationNanos() + ", "
-                        + workDuration.getActualGpuDurationNanos() + ")");
-            }
-            if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) {
-                throw new IllegalArgumentException(
-                    TextUtils.formatSimple(
-                            "Work period start timestamp (%d) should be greater than 0",
-                            workDuration.getWorkPeriodStartTimestampNanos()));
-            }
-            if (workDuration.getActualTotalDurationNanos() <= 0) {
-                throw new IllegalArgumentException(
-                    TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
-                            workDuration.getActualTotalDurationNanos()));
-            }
-            if (workDuration.getActualCpuDurationNanos() <= 0) {
-                throw new IllegalArgumentException(
-                    TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
-                            workDuration.getActualCpuDurationNanos()));
-            }
-            if (workDuration.getActualGpuDurationNanos() < 0) {
-                throw new IllegalArgumentException(
-                    TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
-                            workDuration.getActualGpuDurationNanos()));
-            }
-        }
-
         private void onProcStateChanged(boolean updateAllowed) {
             updateHintAllowed(updateAllowed);
         }
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index aadd03b..894226c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -33,6 +33,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -265,4 +266,11 @@
             ipw.decreaseIndent();
         }
     }
+
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        dump(new IndentingPrintWriter(sw));
+        return sw.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index f9d57e4..a8eda3c 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -44,11 +44,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.power.EnergyConsumerStats;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 
-import libcore.util.EmptyArray;
-
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -128,9 +125,6 @@
     private boolean mUseLatestStates = true;
 
     @GuardedBy("this")
-    private final IntArray mUidsToRemove = new IntArray();
-
-    @GuardedBy("this")
     private Future<?> mWakelockChangesUpdate;
 
     @GuardedBy("this")
@@ -260,7 +254,6 @@
 
     @Override
     public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
-        mUidsToRemove.add(uid);
         return scheduleSyncLocked("remove-uid", UPDATE_CPU);
     }
 
@@ -459,7 +452,6 @@
             // Capture a snapshot of the state we are meant to process.
             final int updateFlags;
             final String reason;
-            final int[] uidsToRemove;
             final boolean onBattery;
             final boolean onBatteryScreenOff;
             final int screenState;
@@ -468,7 +460,6 @@
             synchronized (BatteryExternalStatsWorker.this) {
                 updateFlags = mUpdateFlags;
                 reason = mCurrentReason;
-                uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
                 onBattery = mOnBattery;
                 onBatteryScreenOff = mOnBatteryScreenOff;
                 screenState = mScreenState;
@@ -476,7 +467,6 @@
                 useLatestStates = mUseLatestStates;
                 mUpdateFlags = 0;
                 mCurrentReason = null;
-                mUidsToRemove.clear();
                 mCurrentFuture = null;
                 mUseLatestStates = true;
                 if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
@@ -512,12 +502,6 @@
 
                 // Clean up any UIDs if necessary.
                 synchronized (mStats) {
-                    for (int uid : uidsToRemove) {
-                        FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid,
-                                FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
-                        mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
-                                SystemClock.uptimeMillis());
-                    }
                     mStats.clearPendingRemovedUidsLocked();
                 }
             } catch (Exception e) {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
new file mode 100644
index 0000000..ad146af
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java
@@ -0,0 +1,40 @@
+/*
+ * 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.power.stats;
+
+import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+
+public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper {
+    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+
+    public BatteryStatsDumpHelperImpl(BatteryUsageStatsProvider batteryUsageStatsProvider) {
+        mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+    }
+
+    @Override
+    public BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed) {
+        BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
+                .setMaxStatsAgeMs(0);
+        if (detailed) {
+            builder.includePowerModels().includeProcessStateData().includeVirtualUids();
+        }
+        return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats,
+                builder.build());
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index eea13f1..0491c14 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -185,7 +185,7 @@
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    public static final int VERSION = 213;
+    public static final int VERSION = 214;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -220,6 +220,8 @@
     public static final int RESET_REASON_FULL_CHARGE = 3;
     public static final int RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE = 4;
     public static final int RESET_REASON_PLUGGED_IN_FOR_LONG_DURATION = 5;
+    @NonNull
+    private final MonotonicClock mMonotonicClock;
 
     protected Clock mClock;
 
@@ -393,19 +395,9 @@
         }
     }
 
-    /**
-     * Listener for the battery stats reset.
-     */
-    public interface BatteryResetListener {
-
-        /**
-         * Callback invoked immediately prior to resetting battery stats.
-         * @param resetReason One of the RESET_REASON_* constants.
-         */
-        void prepareForBatteryStatsReset(int resetReason);
-    }
-
-    private BatteryResetListener mBatteryResetListener;
+    private boolean mSaveBatteryUsageStatsOnReset;
+    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private PowerStatsStore mPowerStatsStore;
 
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
@@ -787,13 +779,10 @@
     private BatteryCallback mCallback;
 
     /**
-     * Mapping isolated uids to the actual owning app uid.
+     * Mapping child uids to their parent uid.
      */
-    private final SparseIntArray mIsolatedUids = new SparseIntArray();
-    /**
-     * Internal reference count of isolated uids.
-     */
-    private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+    @VisibleForTesting
+    protected final PowerStatsUidResolver mPowerStatsUidResolver;
 
     /**
      * The statistics we have collected organized by uids.
@@ -874,6 +863,8 @@
     long mUptimeStartUs;
     long mRealtimeUs;
     long mRealtimeStartUs;
+    long mMonotonicStartTime;
+    long mMonotonicEndTime = MonotonicClock.UNDEFINED;
 
     int mWakeLockNesting;
     boolean mWakeLockImportant;
@@ -1724,25 +1715,26 @@
     }
 
     @VisibleForTesting
-    public BatteryStatsImpl(Clock clock, File historyDirectory) {
+    public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler,
+            @NonNull PowerStatsUidResolver powerStatsUidResolver) {
         init(clock);
         mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
-        mHandler = null;
+        mHandler = handler;
+        mPowerStatsUidResolver = powerStatsUidResolver;
         mConstants = new Constants(mHandler);
         mStartClockTimeMs = clock.currentTimeMillis();
         mDailyFile = null;
+        mMonotonicClock = new MonotonicClock(0, mClock);
         if (historyDirectory == null) {
             mCheckinFile = null;
             mStatsFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
-                    new MonotonicClock(0, mClock));
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
         } else {
             mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
             mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
-                    new MonotonicClock(0, mClock));
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
         }
         mPlatformIdleStateCallback = null;
         mEnergyConsumerRetriever = null;
@@ -4278,92 +4270,51 @@
         }
     }
 
-    @GuardedBy("this")
-    public void addIsolatedUidLocked(int isolatedUid, int appUid) {
-        addIsolatedUidLocked(isolatedUid, appUid,
-                mClock.elapsedRealtime(), mClock.uptimeMillis());
+    private void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+        long realtime = mClock.elapsedRealtime();
+        long uptime = mClock.uptimeMillis();
+        synchronized (this) {
+            getUidStatsLocked(parentUid, realtime, uptime).addIsolatedUid(isolatedUid);
+        }
     }
 
-    @GuardedBy("this")
-    @SuppressWarnings("GuardedBy")   // errorprone false positive on u.addIsolatedUid
-    public void addIsolatedUidLocked(int isolatedUid, int appUid,
-            long elapsedRealtimeMs, long uptimeMs) {
-        mIsolatedUids.put(isolatedUid, appUid);
-        mIsolatedUidRefCounts.put(isolatedUid, 1);
-        final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs);
-        u.addIsolatedUid(isolatedUid);
+    private void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+        long realtime = mClock.elapsedRealtime();
+        mPowerStatsUidResolver.retainIsolatedUid(isolatedUid);
+        synchronized (this) {
+            mPendingRemovedUids.add(new UidToRemove(isolatedUid, realtime));
+        }
+        if (mExternalSync != null) {
+            mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid);
+        }
     }
 
-    /**
-     * Schedules a read of the latest cpu times before removing the isolated UID.
-     * @see #removeIsolatedUidLocked(int, int, int)
-     */
-    public void scheduleRemoveIsolatedUidLocked(int isolatedUid, int appUid) {
-        int curUid = mIsolatedUids.get(isolatedUid, -1);
-        if (curUid == appUid) {
-            if (mExternalSync != null) {
-                mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid);
-            }
+    private void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+        long realtime = mClock.elapsedRealtime();
+        long uptime = mClock.uptimeMillis();
+        synchronized (this) {
+            getUidStatsLocked(parentUid, realtime, uptime).removeIsolatedUid(isolatedUid);
         }
     }
 
     /**
      * Isolated uid should only be removed after all wakelocks associated with the uid are stopped
      * and the cpu time-in-state has been read one last time for the uid.
-     *
-     * @see #scheduleRemoveIsolatedUidLocked(int, int)
-     *
-     * @return true if the isolated uid is actually removed.
      */
     @GuardedBy("this")
-    public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs,
-            long uptimeMs) {
-        final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
-        if (refCount > 0) {
-            // Isolated uid is still being tracked
-            mIsolatedUidRefCounts.put(isolatedUid, refCount);
-            return false;
-        }
-
-        final int idx = mIsolatedUids.indexOfKey(isolatedUid);
-        if (idx >= 0) {
-            final int ownerUid = mIsolatedUids.valueAt(idx);
-            final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs);
-            u.removeIsolatedUid(isolatedUid);
-            mIsolatedUids.removeAt(idx);
-            mIsolatedUidRefCounts.delete(isolatedUid);
-        } else {
-            Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")");
-        }
-        mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs));
-
-        return true;
-    }
-
-    /**
-     * Increment the ref count for an isolated uid.
-     * call #maybeRemoveIsolatedUidLocked to decrement.
-     */
-    public void incrementIsolatedUidRefCount(int uid) {
-        final int refCount = mIsolatedUidRefCounts.get(uid, 0);
-        if (refCount <= 0) {
-            // Uid is not mapped or referenced
-            Slog.w(TAG,
-                    "Attempted to increment ref counted of untracked isolated uid (" + uid + ")");
-            return;
-        }
-        mIsolatedUidRefCounts.put(uid, refCount + 1);
+    public void releaseIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) {
+        mPowerStatsUidResolver.releaseIsolatedUid(isolatedUid);
     }
 
     private int mapUid(int uid) {
         if (Process.isSdkSandboxUid(uid)) {
             return Process.getAppUidForSdkSandboxUid(uid);
         }
-        return mapIsolatedUid(uid);
+        return mPowerStatsUidResolver.mapUid(uid);
     }
 
     private int mapIsolatedUid(int uid) {
-        return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid);
+        return mPowerStatsUidResolver.mapUid(uid);
     }
 
     @GuardedBy("this")
@@ -4745,7 +4696,7 @@
             if (mappedUid != uid) {
                 // Prevent the isolated uid mapping from being removed while the wakelock is
                 // being held.
-                incrementIsolatedUidRefCount(uid);
+                mPowerStatsUidResolver.retainIsolatedUid(uid);
             }
             if (mOnBatteryScreenOffTimeBase.isRunning()) {
                 // We only update the cpu time when a wake lock is acquired if the screen is off.
@@ -4825,7 +4776,7 @@
 
             if (mappedUid != uid) {
                 // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
-                maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+                releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
             }
         }
     }
@@ -4996,7 +4947,7 @@
         if (mappedUid != uid) {
             // Prevent the isolated uid mapping from being removed while the wakelock is
             // being held.
-            incrementIsolatedUidRefCount(uid);
+            mPowerStatsUidResolver.retainIsolatedUid(uid);
         }
     }
 
@@ -5048,7 +4999,7 @@
                 historyName, mappedUid);
         if (mappedUid != uid) {
             // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
-            maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+            releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -7642,35 +7593,53 @@
     /**
      * Returns the names of custom power components.
      */
-    @GuardedBy("this")
     @Override
     public @NonNull String[] getCustomEnergyConsumerNames() {
-        if (mEnergyConsumerStatsConfig == null) {
-            return new String[0];
-        }
-        final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames();
-        for (int i = 0; i < names.length; i++) {
-            if (TextUtils.isEmpty(names[i])) {
-                names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i;
+        synchronized (this) {
+            if (mEnergyConsumerStatsConfig == null) {
+                return new String[0];
             }
+            final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames();
+            for (int i = 0; i < names.length; i++) {
+                if (TextUtils.isEmpty(names[i])) {
+                    names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i;
+                }
+            }
+            return names;
         }
-        return names;
     }
 
-    @GuardedBy("this")
-    @Override public long getStartClockTime() {
-        final long currentTimeMs = mClock.currentTimeMillis();
-        if ((currentTimeMs > MILLISECONDS_IN_YEAR
-                && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR))
+    @Override
+    public long getStartClockTime() {
+        synchronized (this) {
+            final long currentTimeMs = mClock.currentTimeMillis();
+            if ((currentTimeMs > MILLISECONDS_IN_YEAR
+                    && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR))
                 || (mStartClockTimeMs > currentTimeMs)) {
-            // If the start clock time has changed by more than a year, then presumably
-            // the previous time was completely bogus.  So we are going to figure out a
-            // new time based on how much time has elapsed since we started counting.
-            mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
-                    currentTimeMs);
-            adjustStartClockTime(currentTimeMs);
+                // If the start clock time has changed by more than a year, then presumably
+                // the previous time was completely bogus.  So we are going to figure out a
+                // new time based on how much time has elapsed since we started counting.
+                mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+                        currentTimeMs);
+                adjustStartClockTime(currentTimeMs);
+            }
+            return mStartClockTimeMs;
         }
-        return mStartClockTimeMs;
+    }
+
+    /**
+     * Returns the monotonic time when the BatteryStats session started.
+     */
+    public long getMonotonicStartTime() {
+        return mMonotonicStartTime;
+    }
+
+    /**
+     * Returns the monotonic time when the BatteryStats session ended, or
+     * {@link MonotonicClock#UNDEFINED} if the session is still ongoing.
+     */
+    public long getMonotonicEndTime() {
+        return mMonotonicEndTime;
     }
 
     @Override public String getStartPlatformVersion() {
@@ -8197,7 +8166,9 @@
             return mProportionalSystemServiceUsage;
         }
 
-        @GuardedBy("mBsi")
+        /**
+         * Adds isolated UID to the list of children.
+         */
         public void addIsolatedUid(int isolatedUid) {
             if (mChildUids == null) {
                 mChildUids = new SparseArray<>();
@@ -8207,6 +8178,9 @@
             mChildUids.put(isolatedUid, new ChildUid());
         }
 
+        /**
+         * Removes isolated UID from the list of children.
+         */
         public void removeIsolatedUid(int isolatedUid) {
             final int idx = mChildUids == null ? -1 : mChildUids.indexOfKey(isolatedUid);
             if (idx < 0) {
@@ -8239,20 +8213,20 @@
 
         @GuardedBy("mBsi")
         private void ensureMultiStateCounters(long timestampMs) {
-            if (mProcStateTimeMs != null) {
-                return;
+            if (mProcStateTimeMs == null) {
+                mProcStateTimeMs =
+                        new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
+                                PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                                mBsi.mCpuScalingPolicies.getScalingStepCount(),
+                                timestampMs);
             }
-
-            mProcStateTimeMs =
-                    new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
-                            PROC_STATE_TIME_COUNTER_STATE_COUNT,
-                            mBsi.mCpuScalingPolicies.getScalingStepCount(),
-                            timestampMs);
-            mProcStateScreenOffTimeMs =
-                    new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
-                            PROC_STATE_TIME_COUNTER_STATE_COUNT,
-                            mBsi.mCpuScalingPolicies.getScalingStepCount(),
-                            timestampMs);
+            if (mProcStateScreenOffTimeMs == null) {
+                mProcStateScreenOffTimeMs =
+                        new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
+                                PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                                mBsi.mCpuScalingPolicies.getScalingStepCount(),
+                                timestampMs);
+            }
         }
 
         @GuardedBy("mBsi")
@@ -10910,15 +10884,18 @@
             @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
             @Nullable EnergyStatsRetriever energyStatsCb,
             @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
-            @NonNull CpuScalingPolicies cpuScalingPolicies) {
+            @NonNull CpuScalingPolicies cpuScalingPolicies,
+            @NonNull PowerStatsUidResolver powerStatsUidResolver) {
         init(clock);
 
         mBatteryStatsConfig = config;
+        mMonotonicClock = monotonicClock;
         mHandler = new MyHandler(handler.getLooper());
         mConstants = new Constants(mHandler);
 
         mPowerProfile = powerProfile;
         mCpuScalingPolicies = cpuScalingPolicies;
+        mPowerStatsUidResolver = powerStatsUidResolver;
 
         initPowerProfile();
 
@@ -10927,17 +10904,17 @@
             mCheckinFile = null;
             mDailyFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
             mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
             mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
             mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
         }
 
         mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
-                () -> mBatteryVoltageMv, mHandler,
+                mPowerStatsUidResolver, () -> mBatteryVoltageMv, mHandler,
                 mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
         mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
 
@@ -10954,6 +10931,23 @@
         mEnergyConsumerRetriever = energyStatsCb;
         mUserInfoProvider = userInfoProvider;
 
+        mPowerStatsUidResolver.addListener(new PowerStatsUidResolver.Listener() {
+            @Override
+            public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+                BatteryStatsImpl.this.onIsolatedUidAdded(isolatedUid, parentUid);
+            }
+
+            @Override
+            public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+                BatteryStatsImpl.this.onBeforeIsolatedUidRemoved(isolatedUid, parentUid);
+            }
+
+            @Override
+            public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+                BatteryStatsImpl.this.onAfterIsolatedUidRemoved(isolatedUid, parentUid);
+            }
+        });
+
         // Notify statsd that the system is initially not in doze.
         mDeviceIdleMode = DEVICE_IDLE_MODE_OFF;
         FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
@@ -11497,6 +11491,7 @@
         mUptimeUs = 0;
         mRealtimeStartUs = realtimeUs;
         mUptimeStartUs = uptimeUs;
+        mMonotonicStartTime = mMonotonicClock.monotonicTime();
     }
 
     void initDischarge(long elapsedRealtimeUs) {
@@ -11517,8 +11512,17 @@
         mDischargeCounter.reset(false, elapsedRealtimeUs);
     }
 
-    public void setBatteryResetListener(BatteryResetListener batteryResetListener) {
-        mBatteryResetListener = batteryResetListener;
+    /**
+     * Associates the BatteryStatsImpl object with a BatteryUsageStatsProvider and PowerStatsStore
+     * to allow for a snapshot of battery usage stats to be taken and stored just before battery
+     * reset.
+     */
+    public void saveBatteryUsageStatsOnReset(
+            @NonNull BatteryUsageStatsProvider batteryUsageStatsProvider,
+            @NonNull PowerStatsStore powerStatsStore) {
+        mSaveBatteryUsageStatsOnReset = true;
+        mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+        mPowerStatsStore = powerStatsStore;
     }
 
     @GuardedBy("this")
@@ -11557,9 +11561,7 @@
     @GuardedBy("this")
     private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis,
             int resetReason) {
-        if (mBatteryResetListener != null) {
-            mBatteryResetListener.prepareForBatteryStatsReset(resetReason);
-        }
+        saveBatteryUsageStatsOnReset(resetReason);
 
         final long uptimeUs = uptimeMillis * 1000;
         final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000;
@@ -11707,6 +11709,31 @@
         mHandler.sendEmptyMessage(MSG_REPORT_RESET_STATS);
     }
 
+    private void saveBatteryUsageStatsOnReset(int resetReason) {
+        if (!mSaveBatteryUsageStatsOnReset
+                || resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+            return;
+        }
+
+        final BatteryUsageStats batteryUsageStats;
+        synchronized (this) {
+            batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this,
+                    new BatteryUsageStatsQuery.Builder()
+                            .setMaxStatsAgeMs(0)
+                            .includePowerModels()
+                            .includeProcessStateData()
+                            .build());
+        }
+
+        // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+        // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+        // start time
+        long monotonicStartTime =
+                mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+        mHandler.post(() ->
+                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+    }
+
     @GuardedBy("this")
     private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
         for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
@@ -15137,6 +15164,7 @@
             if (mKernelSingleUidTimeReader != null) {
                 mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
             }
+            mPowerStatsUidResolver.releaseUidsInRange(startUid, endUid);
             // Treat as one. We don't know how many uids there are in between.
             mNumUidsRemoved++;
         } else {
@@ -15192,10 +15220,11 @@
         mShuttingDown = true;
     }
 
-    @GuardedBy("this")
     @Override
     public boolean isProcessStateDataAvailable() {
-        return trackPerProcStateCpuTimes();
+        synchronized (this) {
+            return trackPerProcStateCpuTimes();
+        }
     }
 
     @GuardedBy("this")
@@ -15862,6 +15891,8 @@
         mUptimeUs = in.readLong();
         mRealtimeUs = in.readLong();
         mStartClockTimeMs = in.readLong();
+        mMonotonicStartTime = in.readLong();
+        mMonotonicEndTime = in.readLong();
         mStartPlatformVersion = in.readString();
         mEndPlatformVersion = in.readString();
         mOnBatteryTimeBase.readSummaryFromParcel(in);
@@ -16382,6 +16413,8 @@
         out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED));
         out.writeLong(computeRealtime(nowRealtime, STATS_SINCE_CHARGED));
         out.writeLong(mStartClockTimeMs);
+        out.writeLong(mMonotonicStartTime);
+        out.writeLong(mMonotonicClock.monotonicTime());
         out.writeString(mStartPlatformVersion);
         out.writeString(mEndPlatformVersion);
         mOnBatteryTimeBase.writeSummaryToParcel(out, nowUptime, nowRealtime);
@@ -16912,7 +16945,8 @@
     }
 
     @GuardedBy("this")
-    public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
+    public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart,
+            BatteryStatsDumpHelper dumpHelper) {
         if (DEBUG) {
             pw.println("mOnBatteryTimeBase:");
             mOnBatteryTimeBase.dump(pw, "  ");
@@ -16984,7 +17018,7 @@
             pr.println("*** Camera timer:");
             mCameraOnTimer.logState(pr, "  ");
         }
-        super.dump(context, pw, flags, reqUid, histStart);
+        super.dump(context, pw, flags, reqUid, histStart, dumpHelper);
 
         synchronized (this) {
             pw.print("Per process state tracking available: ");
@@ -16998,15 +17032,7 @@
             pw.print("UIDs removed since the later of device start or stats reset: ");
             pw.println(mNumUidsRemoved);
 
-            pw.println("Currently mapped isolated uids:");
-            final int numIsolatedUids = mIsolatedUids.size();
-            for (int i = 0; i < numIsolatedUids; i++) {
-                final int isolatedUid = mIsolatedUids.keyAt(i);
-                final int ownerUid = mIsolatedUids.valueAt(i);
-                final int refCount = mIsolatedUidRefCounts.get(isolatedUid);
-                pw.println(
-                        "  " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")");
-            }
+            mPowerStatsUidResolver.dump(pw);
 
             pw.println();
             dumpConstantsLocked(pw);
@@ -17020,15 +17046,4 @@
             dumpEnergyConsumerStatsLocked(pw);
         }
     }
-
-    @Override
-    protected BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed) {
-        final BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, this);
-        BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
-                .setMaxStatsAgeMs(0);
-        if (detailed) {
-            builder.includePowerModels().includeProcessStateData().includeVirtualUids();
-        }
-        return provider.getBatteryUsageStats(builder.build());
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 851a3f7..303c245 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -23,14 +23,12 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
-import android.os.SystemClock;
 import android.os.UidBatteryConsumer;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
@@ -45,27 +43,25 @@
 public class BatteryUsageStatsProvider {
     private static final String TAG = "BatteryUsageStatsProv";
     private final Context mContext;
-    private final BatteryStats mStats;
+    private boolean mPowerStatsExporterEnabled;
+    private final PowerStatsExporter mPowerStatsExporter;
     private final PowerStatsStore mPowerStatsStore;
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
+    private final Clock mClock;
     private final Object mLock = new Object();
     private List<PowerCalculator> mPowerCalculators;
 
-    public BatteryUsageStatsProvider(Context context, BatteryStats stats) {
-        this(context, stats, null);
-    }
-
-    @VisibleForTesting
-    public BatteryUsageStatsProvider(Context context, BatteryStats stats,
-            PowerStatsStore powerStatsStore) {
+    public BatteryUsageStatsProvider(Context context,
+            PowerStatsExporter powerStatsExporter,
+            PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies,
+            PowerStatsStore powerStatsStore, Clock clock) {
         mContext = context;
-        mStats = stats;
+        mPowerStatsExporter = powerStatsExporter;
         mPowerStatsStore = powerStatsStore;
-        mPowerProfile = stats instanceof BatteryStatsImpl
-                ? ((BatteryStatsImpl) stats).getPowerProfile()
-                : new PowerProfile(context);
-        mCpuScalingPolicies = stats.getCpuScalingPolicies();
+        mPowerProfile = powerProfile;
+        mCpuScalingPolicies = cpuScalingPolicies;
+        mClock = clock;
     }
 
     private List<PowerCalculator> getPowerCalculators() {
@@ -75,7 +71,10 @@
 
                 // Power calculators are applied in the order of registration
                 mPowerCalculators.add(new BatteryChargeCalculator());
-                mPowerCalculators.add(new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
+                if (mPowerStatsExporterEnabled) {
+                    mPowerCalculators.add(
+                            new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
+                }
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
                 if (!BatteryStats.checkWifiOnly(mContext)) {
@@ -111,27 +110,28 @@
      * Returns true if the last update was too long ago for the tolerances specified
      * by the supplied queries.
      */
-    public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries,
-            long lastUpdateTimeStampMs) {
+    public static boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries,
+            long elapsedRealtime, long lastUpdateTimeStampMs) {
         long allowableStatsAge = Long.MAX_VALUE;
         for (int i = queries.size() - 1; i >= 0; i--) {
             BatteryUsageStatsQuery query = queries.get(i);
             allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge());
         }
 
-        return elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge;
+        return elapsedRealtime - lastUpdateTimeStampMs > allowableStatsAge;
     }
 
     /**
      * Returns snapshots of battery attribution data, one per supplied query.
      */
-    public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+    public List<BatteryUsageStats> getBatteryUsageStats(BatteryStatsImpl stats,
+            List<BatteryUsageStatsQuery> queries) {
         ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
-        synchronized (mStats) {
-            mStats.prepareForDumpLocked();
-            final long currentTimeMillis = currentTimeMillis();
+        synchronized (stats) {
+            stats.prepareForDumpLocked();
+            final long currentTimeMillis = mClock.currentTimeMillis();
             for (int i = 0; i < queries.size(); i++) {
-                results.add(getBatteryUsageStats(queries.get(i), currentTimeMillis));
+                results.add(getBatteryUsageStats(stats, queries.get(i), currentTimeMillis));
             }
         }
         return results;
@@ -140,58 +140,59 @@
     /**
      * Returns a snapshot of battery attribution data.
      */
-    @VisibleForTesting
-    public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
-        synchronized (mStats) {
-            return getBatteryUsageStats(query, currentTimeMillis());
-        }
+    public BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
+            BatteryUsageStatsQuery query) {
+        return getBatteryUsageStats(stats, query, mClock.currentTimeMillis());
     }
 
-    @GuardedBy("mStats")
-    private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
-            long currentTimeMs) {
+    private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
+            BatteryUsageStatsQuery query, long currentTimeMs) {
         if (query.getToTimestamp() == 0) {
-            return getCurrentBatteryUsageStats(query, currentTimeMs);
+            return getCurrentBatteryUsageStats(stats, query, currentTimeMs);
         } else {
-            return getAggregatedBatteryUsageStats(query);
+            return getAggregatedBatteryUsageStats(stats, query);
         }
     }
 
-    @GuardedBy("mStats")
-    private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query,
-            long currentTimeMs) {
-        final long realtimeUs = elapsedRealtime() * 1000;
-        final long uptimeUs = uptimeMillis() * 1000;
+    private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats,
+            BatteryUsageStatsQuery query, long currentTimeMs) {
+        final long realtimeUs = mClock.elapsedRealtime() * 1000;
+        final long uptimeUs = mClock.uptimeMillis() * 1000;
 
         final boolean includePowerModels = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
-                && mStats.isProcessStateDataAvailable();
+                && stats.isProcessStateDataAvailable();
         final boolean includeVirtualUids =  ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
-                mStats.getCustomEnergyConsumerNames(), includePowerModels,
+                stats.getCustomEnergyConsumerNames(), includePowerModels,
                 includeProcessStateData, minConsumedPowerThreshold);
         // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
-        // of stats sessions to wall-clock adjustments
-        batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime());
+        // of batteryUsageStats sessions to wall-clock adjustments
+        batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime());
         batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs);
 
-        SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
-        for (int i = uidStats.size() - 1; i >= 0; i--) {
-            final BatteryStats.Uid uid = uidStats.valueAt(i);
-            if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) {
-                continue;
-            }
+        synchronized (stats) {
+            SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats();
+            for (int i = uidStats.size() - 1; i >= 0; i--) {
+                final BatteryStats.Uid uid = uidStats.valueAt(i);
+                if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) {
+                    continue;
+                }
 
-            batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid)
-                    .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND,
-                            getProcessBackgroundTimeMs(uid, realtimeUs))
-                    .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND,
-                            getProcessForegroundTimeMs(uid, realtimeUs));
+                batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid)
+                        .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND,
+                                getProcessBackgroundTimeMs(uid, realtimeUs))
+                        .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND,
+                                getProcessForegroundTimeMs(uid, realtimeUs))
+                        .setTimeInProcessStateMs(
+                                UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+                                getProcessForegroundServiceTimeMs(uid, realtimeUs));
+            }
         }
 
         final int[] powerComponents = query.getPowerComponents();
@@ -200,8 +201,8 @@
             PowerCalculator powerCalculator = powerCalculators.get(i);
             if (powerComponents != null) {
                 boolean include = false;
-                for (int j = 0; j < powerComponents.length; j++) {
-                    if (powerCalculator.isPowerComponentSupported(powerComponents[j])) {
+                for (int powerComponent : powerComponents) {
+                    if (powerCalculator.isPowerComponentSupported(powerComponent)) {
                         include = true;
                         break;
                     }
@@ -210,26 +211,24 @@
                     continue;
                 }
             }
-            powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
-                    query);
+            powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, query);
+        }
+
+        if (mPowerStatsExporterEnabled) {
+            mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder,
+                    stats.getMonotonicStartTime(), stats.getMonotonicEndTime());
         }
 
         if ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
-            if (!(mStats instanceof BatteryStatsImpl)) {
-                throw new UnsupportedOperationException(
-                        "History cannot be included for " + getClass().getName());
-            }
-
-            BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory());
+            batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
         }
 
-        BatteryUsageStats stats = batteryUsageStatsBuilder.build();
+        BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
         if (includeProcessStateData) {
-            verify(stats);
+            verify(batteryUsageStats);
         }
-        return stats;
+        return batteryUsageStats;
     }
 
     // STOPSHIP(b/229906525): remove verification before shipping
@@ -295,22 +294,27 @@
     }
 
     private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
-        return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
+        return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
                 realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
-                + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
-                realtimeUs, BatteryStats.STATS_SINCE_CHARGED))
                 / 1000;
     }
 
-    private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
+    private long getProcessForegroundServiceTimeMs(BatteryStats.Uid uid, long realtimeUs) {
+        return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
+                realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
+                / 1000;
+    }
+
+    private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats,
+            BatteryUsageStatsQuery query) {
         final boolean includePowerModels = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
-                && mStats.isProcessStateDataAvailable();
+                && stats.isProcessStateDataAvailable();
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
-        final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
+        final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 customEnergyConsumerNames, includePowerModels, includeProcessStateData,
                 minConsumedPowerThreshold);
@@ -380,27 +384,8 @@
         return builder.build();
     }
 
-    private long elapsedRealtime() {
-        if (mStats instanceof BatteryStatsImpl) {
-            return ((BatteryStatsImpl) mStats).mClock.elapsedRealtime();
-        } else {
-            return SystemClock.elapsedRealtime();
-        }
-    }
+    public void setPowerStatsExporterEnabled(boolean enabled) {
 
-    private long uptimeMillis() {
-        if (mStats instanceof BatteryStatsImpl) {
-            return ((BatteryStatsImpl) mStats).mClock.uptimeMillis();
-        } else {
-            return SystemClock.uptimeMillis();
-        }
-    }
-
-    private long currentTimeMillis() {
-        if (mStats instanceof BatteryStatsImpl) {
-            return ((BatteryStatsImpl) mStats).mClock.currentTimeMillis();
-        } else {
-            return System.currentTimeMillis();
-        }
+        mPowerStatsExporterEnabled = enabled;
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
index f40eef2..ed9414f 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
@@ -64,7 +64,7 @@
     private PowerStats.Descriptor mLastUsedDescriptor;
     // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when
     // mLastUsedDescriptor changes
-    private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+    private CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout;
     // Sequence of steps for power estimation and intermediate results.
     private PowerEstimationPlan mPlan;
 
@@ -106,7 +106,7 @@
         }
 
         mLastUsedDescriptor = descriptor;
-        mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+        mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
         mStatsLayout.fromExtras(descriptor.extras);
 
         mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
@@ -149,7 +149,7 @@
 
         if (mPlan == null) {
             mPlan = new PowerEstimationPlan(stats.getConfig());
-            if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) {
+            if (mStatsLayout.getEnergyConsumerCount() != 0) {
                 initEnergyConsumerToPowerBracketMaps();
             }
         }
@@ -212,7 +212,7 @@
      *          CL_2: [bracket3, bracket4]
      */
     private void initEnergyConsumerToPowerBracketMaps() {
-        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
         int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
 
         mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
@@ -294,7 +294,7 @@
                 continue;
             }
 
-            intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray);
+            intermediates.uptime += mStatsLayout.getUsageDuration(mTmpDeviceStatsArray);
 
             for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
                 intermediates.timeByCluster[cluster] +=
@@ -351,7 +351,7 @@
         int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
         int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
         int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
-        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
         List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations;
         for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) {
             DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse);
@@ -392,7 +392,7 @@
 
     private void adjustEstimatesUsingEnergyConsumers(
             Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) {
-        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
         if (energyConsumerCount == 0) {
             return;
         }
@@ -509,8 +509,8 @@
             }
             sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
         }
-        sb.append("] uptime: ").append(mStatsLayout.getUptime(stats));
-        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats));
+        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
         if (energyConsumerCount > 0) {
             sb.append(" energy: [");
             for (int i = 0; i < energyConsumerCount; i++) {
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b8e581f..c05407c 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -22,6 +22,7 @@
 import android.os.BatteryConsumer;
 import android.os.Handler;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.power.PowerStatsInternal;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -67,6 +68,7 @@
     private final CpuScalingPolicies mCpuScalingPolicies;
     private final PowerProfile mPowerProfile;
     private final KernelCpuStatsReader mKernelCpuStatsReader;
+    private final PowerStatsUidResolver mUidResolver;
     private final Supplier<PowerStatsInternal> mPowerStatsSupplier;
     private final IntSupplier mVoltageSupplier;
     private final int mDefaultCpuPowerBrackets;
@@ -81,7 +83,7 @@
     private PowerStats.Descriptor mPowerStatsDescriptor;
     // Reusable instance
     private PowerStats mCpuPowerStats;
-    private StatsArrayLayout mLayout;
+    private CpuStatsArrayLayout mLayout;
     private long mLastUpdateTimestampNanos;
     private long mLastUpdateUptimeMillis;
     private int mLastVoltageMv;
@@ -91,55 +93,30 @@
      * Captures the positions and lengths of sections of the stats array, such as time-in-state,
      * power usage estimates etc.
      */
-    public static class StatsArrayLayout {
+    public static class CpuStatsArrayLayout extends StatsArrayLayout {
         private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt";
         private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc";
         private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc";
         private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc";
-        private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
-        private static final String EXTRA_DEVICE_UPTIME_POSITION = "du";
-        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
-        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
         private static final String EXTRA_UID_BRACKETS_POSITION = "ub";
         private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us";
-        private static final String EXTRA_UID_POWER_POSITION = "up";
-
-        private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
-
-        private int mDeviceStatsArrayLength;
-        private int mUidStatsArrayLength;
 
         private int mDeviceCpuTimeByScalingStepPosition;
         private int mDeviceCpuTimeByScalingStepCount;
         private int mDeviceCpuTimeByClusterPosition;
         private int mDeviceCpuTimeByClusterCount;
-        private int mDeviceCpuUptimePosition;
-        private int mDeviceEnergyConsumerPosition;
-        private int mDeviceEnergyConsumerCount;
-        private int mDevicePowerEstimatePosition;
 
         private int mUidPowerBracketsPosition;
         private int mUidPowerBracketCount;
-        private int[][] mEnergyConsumerToPowerBucketMaps;
-        private int mUidPowerEstimatePosition;
 
         private int[] mScalingStepToPowerBracketMap;
 
-        public int getDeviceStatsArrayLength() {
-            return mDeviceStatsArrayLength;
-        }
-
-        public int getUidStatsArrayLength() {
-            return mUidStatsArrayLength;
-        }
-
         /**
          * Declare that the stats array has a section capturing CPU time per scaling step
          */
         public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
-            mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength;
+            mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
             mDeviceCpuTimeByScalingStepCount = scalingStepCount;
-            mDeviceStatsArrayLength += scalingStepCount;
         }
 
         public int getCpuScalingStepCount() {
@@ -166,9 +143,8 @@
          * Declare that the stats array has a section capturing CPU time in each cluster
          */
         public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
+            mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
             mDeviceCpuTimeByClusterCount = clusterCount;
-            mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength;
-            mDeviceStatsArrayLength += clusterCount;
         }
 
         public int getCpuClusterCount() {
@@ -192,86 +168,12 @@
         }
 
         /**
-         * Declare that the stats array has a section capturing CPU uptime
-         */
-        public void addDeviceSectionUptime() {
-            mDeviceCpuUptimePosition = mDeviceStatsArrayLength++;
-        }
-
-        /**
-         * Saves the CPU uptime duration in the corresponding <code>stats</code> element.
-         */
-        public void setUptime(long[] stats, long value) {
-            stats[mDeviceCpuUptimePosition] = value;
-        }
-
-        /**
-         * Extracts the CPU uptime duration from the corresponding <code>stats</code> element.
-         */
-        public long getUptime(long[] stats) {
-            return stats[mDeviceCpuUptimePosition];
-        }
-
-        /**
-         * Declares that the stats array has a section capturing EnergyConsumer data from
-         * PowerStatsService.
-         */
-        public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
-            mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength;
-            mDeviceEnergyConsumerCount = energyConsumerCount;
-            mDeviceStatsArrayLength += energyConsumerCount;
-        }
-
-        public int getCpuClusterEnergyConsumerCount() {
-            return mDeviceEnergyConsumerCount;
-        }
-
-        /**
-         * Saves the accumulated energy for the specified rail the corresponding
-         * <code>stats</code> element.
-         */
-        public void setConsumedEnergy(long[] stats, int index, long energy) {
-            stats[mDeviceEnergyConsumerPosition + index] = energy;
-        }
-
-        /**
-         * Extracts the EnergyConsumer data from a device stats array for the specified
-         * EnergyConsumer.
-         */
-        public long getConsumedEnergy(long[] stats, int index) {
-            return stats[mDeviceEnergyConsumerPosition + index];
-        }
-
-        /**
-         * Declare that the stats array has a section capturing a power estimate
-         */
-        public void addDeviceSectionPowerEstimate() {
-            mDevicePowerEstimatePosition = mDeviceStatsArrayLength++;
-        }
-
-        /**
-         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
-         * element of <code>stats</code>.
-         */
-        public void setDevicePowerEstimate(long[] stats, double power) {
-            stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
-        }
-
-        /**
-         * Extracts the power estimate from a device stats array and converts it to mAh.
-         */
-        public double getDevicePowerEstimate(long[] stats) {
-            return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
-        }
-
-        /**
          * Declare that the UID stats array has a section capturing CPU time per power bracket.
          */
         public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
             mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
-            mUidPowerBracketsPosition = mUidStatsArrayLength;
             updatePowerBracketCount();
-            mUidStatsArrayLength += mUidPowerBracketCount;
+            mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
         }
 
         private void updatePowerBracketCount() {
@@ -306,31 +208,10 @@
         }
 
         /**
-         * Declare that the UID stats array has a section capturing a power estimate
-         */
-        public void addUidSectionPowerEstimate() {
-            mUidPowerEstimatePosition = mUidStatsArrayLength++;
-        }
-
-        /**
-         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
-         * element of <code>stats</code>.
-         */
-        public void setUidPowerEstimate(long[] stats, double power) {
-            stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
-        }
-
-        /**
-         * Extracts the power estimate from a UID stats array and converts it to mAh.
-         */
-        public double getUidPowerEstimate(long[] stats) {
-            return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
-        }
-
-        /**
          * Copies the elements of the stats array layout into <code>extras</code>
          */
         public void toExtras(PersistableBundle extras) {
+            super.toExtras(extras);
             extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
                     mDeviceCpuTimeByScalingStepPosition);
             extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
@@ -339,22 +220,16 @@
                     mDeviceCpuTimeByClusterPosition);
             extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
                     mDeviceCpuTimeByClusterCount);
-            extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition);
-            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
-                    mDeviceEnergyConsumerPosition);
-            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
-                    mDeviceEnergyConsumerCount);
-            extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
             extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
-            extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
+            putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
                     mScalingStepToPowerBracketMap);
-            extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
         }
 
         /**
          * Retrieves elements of the stats array layout from <code>extras</code>
          */
         public void fromExtras(PersistableBundle extras) {
+            super.fromExtras(extras);
             mDeviceCpuTimeByScalingStepPosition =
                     extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
             mDeviceCpuTimeByScalingStepCount =
@@ -363,43 +238,39 @@
                     extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
             mDeviceCpuTimeByClusterCount =
                     extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
-            mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION);
-            mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
-            mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
-            mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
             mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
             mScalingStepToPowerBracketMap =
-                    extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
+                    getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
             if (mScalingStepToPowerBracketMap == null) {
                 mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
             }
             updatePowerBracketCount();
-            mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
         }
     }
 
     public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
-            IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) {
-        this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(),
+            PowerStatsUidResolver uidResolver, IntSupplier voltageSupplier, Handler handler,
+            long throttlePeriodMs) {
+        this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), uidResolver,
                 () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier,
                 throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS,
                 DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER);
     }
 
     public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
-                                  Handler handler, KernelCpuStatsReader kernelCpuStatsReader,
-            Supplier<PowerStatsInternal> powerStatsSupplier,
+            Handler handler, KernelCpuStatsReader kernelCpuStatsReader,
+            PowerStatsUidResolver uidResolver, Supplier<PowerStatsInternal> powerStatsSupplier,
             IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock,
             int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) {
         super(handler, throttlePeriodMs, clock);
         mCpuScalingPolicies = cpuScalingPolicies;
         mPowerProfile = powerProfile;
         mKernelCpuStatsReader = kernelCpuStatsReader;
+        mUidResolver = uidResolver;
         mPowerStatsSupplier = powerStatsSupplier;
         mVoltageSupplier = voltageSupplier;
         mDefaultCpuPowerBrackets = defaultCpuPowerBrackets;
         mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
-
     }
 
     /**
@@ -409,13 +280,13 @@
         setEnabled(Flags.streamlinedBatteryStats());
     }
 
-    private void ensureInitialized() {
+    private boolean ensureInitialized() {
         if (mIsInitialized) {
-            return;
+            return true;
         }
 
         if (!isEnabled()) {
-            return;
+            return false;
         }
 
         mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature();
@@ -432,10 +303,10 @@
         mTempCpuTimeByScalingStep = new long[cpuScalingStepCount];
         int[] scalingStepToPowerBracketMap = initPowerBrackets();
 
-        mLayout = new StatsArrayLayout();
+        mLayout = new CpuStatsArrayLayout();
         mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount);
         mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length);
-        mLayout.addDeviceSectionUptime();
+        mLayout.addDeviceSectionUsageDuration();
         mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length);
         mLayout.addDeviceSectionPowerEstimate();
         mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap);
@@ -451,6 +322,7 @@
         mTempUidStats = new long[mLayout.getCpuPowerBracketCount()];
 
         mIsInitialized = true;
+        return true;
     }
 
     private void readCpuEnergyConsumerIds() {
@@ -590,7 +462,9 @@
      * Prints the definitions of power brackets.
      */
     public void dumpCpuPowerBracketsLocked(PrintWriter pw) {
-        ensureInitialized();
+        if (!ensureInitialized()) {
+            return;
+        }
 
         if (mLayout == null) {
             return;
@@ -610,7 +484,9 @@
      */
     @VisibleForTesting
     public String getCpuPowerBracketDescription(int powerBracket) {
-        ensureInitialized();
+        if (!ensureInitialized()) {
+            return "";
+        }
 
         int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap();
         StringBuilder sb = new StringBuilder();
@@ -647,14 +523,18 @@
      */
     @VisibleForTesting
     public PowerStats.Descriptor getPowerStatsDescriptor() {
-        ensureInitialized();
+        if (!ensureInitialized()) {
+            return null;
+        }
 
         return mPowerStatsDescriptor;
     }
 
     @Override
     protected PowerStats collectStats() {
-        ensureInitialized();
+        if (!ensureInitialized()) {
+            return null;
+        }
 
         if (!mIsPerUidTimeInStateSupported) {
             return null;
@@ -682,7 +562,7 @@
         if (uptimeDelta > mCpuPowerStats.durationMs) {
             uptimeDelta = mCpuPowerStats.durationMs;
         }
-        mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta);
+        mLayout.setUsageDuration(mCpuPowerStats.stats, uptimeDelta);
 
         if (mCpuEnergyConsumerIds.length != 0) {
             collectEnergyConsumers();
@@ -761,7 +641,21 @@
             uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket];
         }
         if (nonzero) {
-            mCpuPowerStats.uidStats.put(uid, uidStats.stats);
+            int ownerUid;
+            if (Process.isSdkSandboxUid(uid)) {
+                ownerUid = Process.getAppUidForSdkSandboxUid(uid);
+            } else {
+                ownerUid = mUidResolver.mapUid(uid);
+            }
+
+            long[] ownerStats = mCpuPowerStats.uidStats.get(ownerUid);
+            if (ownerStats == null) {
+                mCpuPowerStats.uidStats.put(ownerUid, uidStats.stats);
+            } else {
+                for (int i = 0; i < ownerStats.length; i++) {
+                    ownerStats[i] += uidStats.stats[i];
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 2c7843e..0facb9c 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -44,6 +44,7 @@
     private static final String XML_TAG_DEVICE_STATS = "device-stats";
     private static final String XML_TAG_UID_STATS = "uid-stats";
     private static final String XML_ATTR_UID = "uid";
+    private static final long UNKNOWN = -1;
 
     public final int powerComponentId;
     private final MultiStateStats.States[] mDeviceStateConfig;
@@ -51,17 +52,16 @@
     @NonNull
     private final AggregatedPowerStatsConfig.PowerComponent mConfig;
     private final int[] mDeviceStates;
-    private final long[] mDeviceStateTimestamps;
 
     private MultiStateStats.Factory mStatsFactory;
     private MultiStateStats.Factory mUidStatsFactory;
     private PowerStats.Descriptor mPowerStatsDescriptor;
+    private long mPowerStatsTimestamp;
     private MultiStateStats mDeviceStats;
     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
 
     private static class UidStats {
         public int[] states;
-        public long[] stateTimestampMs;
         public MultiStateStats stats;
     }
 
@@ -71,7 +71,7 @@
         mDeviceStateConfig = config.getDeviceStateConfig();
         mUidStateConfig = config.getUidStateConfig();
         mDeviceStates = new int[mDeviceStateConfig.length];
-        mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
+        mPowerStatsTimestamp = UNKNOWN;
     }
 
     @NonNull
@@ -85,8 +85,11 @@
     }
 
     void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
+        if (mDeviceStats == null) {
+            createDeviceStats();
+        }
+
         mDeviceStates[stateId] = state;
-        mDeviceStateTimestamps[stateId] = time;
 
         if (mDeviceStateConfig[stateId].isTracked()) {
             if (mDeviceStats != null) {
@@ -97,6 +100,11 @@
         if (mUidStateConfig[stateId].isTracked()) {
             for (int i = mUidStats.size() - 1; i >= 0; i--) {
                 PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
+                if (uidStats.stats == null) {
+                    createUidStats(uidStats);
+                }
+
+                uidStats.states[stateId] = state;
                 if (uidStats.stats != null) {
                     uidStats.stats.setState(stateId, state, time);
                 }
@@ -111,8 +119,11 @@
         }
 
         UidStats uidStats = getUidStats(uid);
+        if (uidStats.stats == null) {
+            createUidStats(uidStats);
+        }
+
         uidStats.states[stateId] = state;
-        uidStats.stateTimestampMs[stateId] = time;
 
         if (uidStats.stats != null) {
             uidStats.stats.setState(stateId, state, time);
@@ -150,10 +161,11 @@
             }
             uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
         }
+
+        mPowerStatsTimestamp = timestampMs;
     }
 
     void reset() {
-        mPowerStatsDescriptor = null;
         mStatsFactory = null;
         mUidStatsFactory = null;
         mDeviceStats = null;
@@ -163,12 +175,10 @@
     }
 
     private UidStats getUidStats(int uid) {
-        // TODO(b/292247660): map isolated and sandbox UIDs
         UidStats uidStats = mUidStats.get(uid);
         if (uidStats == null) {
             uidStats = new UidStats();
             uidStats.states = new int[mUidStateConfig.length];
-            uidStats.stateTimestampMs = new long[mUidStateConfig.length];
             mUidStats.put(uid, uidStats);
         }
         return uidStats;
@@ -209,42 +219,38 @@
         return false;
     }
 
-    private boolean createDeviceStats() {
+    private void createDeviceStats() {
         if (mStatsFactory == null) {
             if (mPowerStatsDescriptor == null) {
-                return false;
+                return;
             }
             mStatsFactory = new MultiStateStats.Factory(
                     mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
         }
 
         mDeviceStats = mStatsFactory.create();
-        for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
-            mDeviceStats.setState(stateId, mDeviceStates[stateId],
-                    mDeviceStateTimestamps[stateId]);
+        if (mPowerStatsTimestamp != UNKNOWN) {
+            for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+                mDeviceStats.setState(stateId, mDeviceStates[stateId], mPowerStatsTimestamp);
+            }
         }
-        return true;
     }
 
-    private boolean createUidStats(UidStats uidStats) {
+    private void createUidStats(UidStats uidStats) {
         if (mUidStatsFactory == null) {
             if (mPowerStatsDescriptor == null) {
-                return false;
+                return;
             }
             mUidStatsFactory = new MultiStateStats.Factory(
                     mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
         }
 
         uidStats.stats = mUidStatsFactory.create();
-        for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
-            uidStats.stats.setState(stateId, mDeviceStates[stateId],
-                    mDeviceStateTimestamps[stateId]);
+        for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) {
+            if (mPowerStatsTimestamp != UNKNOWN) {
+                uidStats.stats.setState(stateId, uidStats.states[stateId], mPowerStatsTimestamp);
+            }
         }
-        for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) {
-            uidStats.stats.setState(stateId, uidStats.states[stateId],
-                    uidStats.stateTimestampMs[stateId]);
-        }
-        return true;
     }
 
     public void writeXml(TypedXmlSerializer serializer) throws IOException {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 2f9d567..3f88a2d 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -29,13 +29,18 @@
  * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
  */
 public class PowerStatsAggregator {
+    private static final long UNINITIALIZED = -1;
     private final AggregatedPowerStats mStats;
+    private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
     private final BatteryStatsHistory mHistory;
     private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>();
+    private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+    private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
 
     public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
             BatteryStatsHistory history) {
         mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+        mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
         mHistory = history;
         for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig :
                 aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) {
@@ -44,6 +49,10 @@
         }
     }
 
+    AggregatedPowerStatsConfig getConfig() {
+        return mAggregatedPowerStatsConfig;
+    }
+
     /**
      * Iterates of the battery history and aggregates power stats between the specified times.
      * The start and end are specified in the battery-stats monotonic time, which is the
@@ -58,18 +67,20 @@
      */
     public void aggregatePowerStats(long startTimeMs, long endTimeMs,
             Consumer<AggregatedPowerStats> consumer) {
-        int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-        int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-        long baseTime = -1;
+        boolean clockUpdateAdded = false;
+        long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED;
         long lastTime = 0;
         try (BatteryStatsHistoryIterator iterator =
                      mHistory.copy().iterate(startTimeMs, endTimeMs)) {
             while (iterator.hasNext()) {
                 BatteryStats.HistoryItem item = iterator.next();
 
-                if (baseTime < 0) {
+                if (!clockUpdateAdded) {
                     mStats.addClockUpdate(item.time, item.currentTime);
-                    baseTime = item.time;
+                    if (baseTime == UNINITIALIZED) {
+                        baseTime = item.time;
+                    }
+                    clockUpdateAdded = true;
                 } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
                            || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
                     mStats.addClockUpdate(item.time, item.currentTime);
@@ -81,20 +92,20 @@
                         (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
                                 ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
                                 : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-                if (batteryState != currentBatteryState) {
+                if (batteryState != mCurrentBatteryState) {
                     mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
                             item.time);
-                    currentBatteryState = batteryState;
+                    mCurrentBatteryState = batteryState;
                 }
 
                 int screenState =
                         (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
                                 ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
                                 : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-                if (screenState != currentScreenState) {
+                if (screenState != mCurrentScreenState) {
                     mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
                             item.time);
-                    currentScreenState = screenState;
+                    mCurrentScreenState = screenState;
                 }
 
                 if (item.processStateChange != null) {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index 84cc21e..abfe9de 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -16,10 +16,13 @@
 
 package com.android.server.power.stats;
 
+import android.annotation.Nullable;
 import android.os.ConditionVariable;
 import android.os.Handler;
+import android.os.PersistableBundle;
 import android.util.FastImmutableArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.Clock;
@@ -38,6 +41,7 @@
  * except where noted.
  */
 public abstract class PowerStatsCollector {
+    private static final String TAG = "PowerStatsCollector";
     private static final int MILLIVOLTS_PER_VOLT = 1000;
     private final Handler mHandler;
     protected final Clock mClock;
@@ -46,6 +50,200 @@
     private boolean mEnabled;
     private long mLastScheduledUpdateMs = -1;
 
+    /**
+     * Captures the positions and lengths of sections of the stats array, such as usage duration,
+     * power usage estimates etc.
+     */
+    public static class StatsArrayLayout {
+        private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
+        private static final String EXTRA_DEVICE_DURATION_POSITION = "dd";
+        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
+        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
+        private static final String EXTRA_UID_POWER_POSITION = "up";
+
+        protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+
+        private int mDeviceStatsArrayLength;
+        private int mUidStatsArrayLength;
+
+        protected int mDeviceDurationPosition;
+        private int mDeviceEnergyConsumerPosition;
+        private int mDeviceEnergyConsumerCount;
+        private int mDevicePowerEstimatePosition;
+        private int mUidPowerEstimatePosition;
+
+        public int getDeviceStatsArrayLength() {
+            return mDeviceStatsArrayLength;
+        }
+
+        public int getUidStatsArrayLength() {
+            return mUidStatsArrayLength;
+        }
+
+        protected int addDeviceSection(int length) {
+            int position = mDeviceStatsArrayLength;
+            mDeviceStatsArrayLength += length;
+            return position;
+        }
+
+        protected int addUidSection(int length) {
+            int position = mUidStatsArrayLength;
+            mUidStatsArrayLength += length;
+            return position;
+        }
+
+        /**
+         * Declare that the stats array has a section capturing usage duration
+         */
+        public void addDeviceSectionUsageDuration() {
+            mDeviceDurationPosition = addDeviceSection(1);
+        }
+
+        /**
+         * Saves the usage duration in the corresponding <code>stats</code> element.
+         */
+        public void setUsageDuration(long[] stats, long value) {
+            stats[mDeviceDurationPosition] = value;
+        }
+
+        /**
+         * Extracts the usage duration from the corresponding <code>stats</code> element.
+         */
+        public long getUsageDuration(long[] stats) {
+            return stats[mDeviceDurationPosition];
+        }
+
+        /**
+         * Declares that the stats array has a section capturing EnergyConsumer data from
+         * PowerStatsService.
+         */
+        public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
+            mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
+            mDeviceEnergyConsumerCount = energyConsumerCount;
+        }
+
+        public int getEnergyConsumerCount() {
+            return mDeviceEnergyConsumerCount;
+        }
+
+        /**
+         * Saves the accumulated energy for the specified rail the corresponding
+         * <code>stats</code> element.
+         */
+        public void setConsumedEnergy(long[] stats, int index, long energy) {
+            stats[mDeviceEnergyConsumerPosition + index] = energy;
+        }
+
+        /**
+         * Extracts the EnergyConsumer data from a device stats array for the specified
+         * EnergyConsumer.
+         */
+        public long getConsumedEnergy(long[] stats, int index) {
+            return stats[mDeviceEnergyConsumerPosition + index];
+        }
+
+        /**
+         * Declare that the stats array has a section capturing a power estimate
+         */
+        public void addDeviceSectionPowerEstimate() {
+            mDevicePowerEstimatePosition = addDeviceSection(1);
+        }
+
+        /**
+         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+         * element of <code>stats</code>.
+         */
+        public void setDevicePowerEstimate(long[] stats, double power) {
+            stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+        }
+
+        /**
+         * Extracts the power estimate from a device stats array and converts it to mAh.
+         */
+        public double getDevicePowerEstimate(long[] stats) {
+            return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+        }
+
+        /**
+         * Declare that the UID stats array has a section capturing a power estimate
+         */
+        public void addUidSectionPowerEstimate() {
+            mUidPowerEstimatePosition = addUidSection(1);
+        }
+
+        /**
+         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+         * element of <code>stats</code>.
+         */
+        public void setUidPowerEstimate(long[] stats, double power) {
+            stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+        }
+
+        /**
+         * Extracts the power estimate from a UID stats array and converts it to mAh.
+         */
+        public double getUidPowerEstimate(long[] stats) {
+            return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+        }
+
+        /**
+         * Copies the elements of the stats array layout into <code>extras</code>
+         */
+        public void toExtras(PersistableBundle extras) {
+            extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
+            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
+                    mDeviceEnergyConsumerPosition);
+            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
+                    mDeviceEnergyConsumerCount);
+            extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+            extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+        }
+
+        /**
+         * Retrieves elements of the stats array layout from <code>extras</code>
+         */
+        public void fromExtras(PersistableBundle extras) {
+            mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
+            mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
+            mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
+            mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+            mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
+        }
+
+        protected void putIntArray(PersistableBundle extras, String key, int[] array) {
+            if (array == null) {
+                return;
+            }
+
+            StringBuilder sb = new StringBuilder();
+            for (int value : array) {
+                if (!sb.isEmpty()) {
+                    sb.append(',');
+                }
+                sb.append(value);
+            }
+            extras.putString(key, sb.toString());
+        }
+
+        protected int[] getIntArray(PersistableBundle extras, String key) {
+            String string = extras.getString(key);
+            if (string == null) {
+                return null;
+            }
+            String[] values = string.trim().split(",");
+            int[] result = new int[values.length];
+            for (int i = 0; i < values.length; i++) {
+                try {
+                    result[i] = Integer.parseInt(values[i]);
+                } catch (NumberFormatException e) {
+                    Slog.wtf(TAG, "Invalid CSV format: " + string);
+                    return null;
+                }
+            }
+            return result;
+        }
+    }
+
     @GuardedBy("this")
     @SuppressWarnings("unchecked")
     private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList =
@@ -141,6 +339,7 @@
         return true;
     }
 
+    @Nullable
     protected abstract PowerStats collectStats();
 
     /**
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
new file mode 100644
index 0000000..70c24d5
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -0,0 +1,227 @@
+/*
+ * 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.power.stats;
+
+import android.os.AggregateBatteryConsumer;
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
+import android.os.UidBatteryConsumer;
+import android.util.Slog;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Given a time range, converts accumulated PowerStats to BatteryUsageStats.  Combines
+ * stores spans of PowerStats with the yet-unprocessed tail of battery history.
+ */
+public class PowerStatsExporter {
+    private static final String TAG = "PowerStatsExporter";
+    private final PowerStatsStore mPowerStatsStore;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private final long mBatterySessionTimeSpanSlackMillis;
+    private static final long BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS = TimeUnit.MINUTES.toMillis(2);
+
+    public PowerStatsExporter(PowerStatsStore powerStatsStore,
+            PowerStatsAggregator powerStatsAggregator) {
+        this(powerStatsStore, powerStatsAggregator, BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS);
+    }
+
+    public PowerStatsExporter(PowerStatsStore powerStatsStore,
+            PowerStatsAggregator powerStatsAggregator,
+            long batterySessionTimeSpanSlackMillis) {
+        mPowerStatsStore = powerStatsStore;
+        mPowerStatsAggregator = powerStatsAggregator;
+        mBatterySessionTimeSpanSlackMillis = batterySessionTimeSpanSlackMillis;
+    }
+
+    /**
+     * Populates the provided BatteryUsageStats.Builder with power estimates from the accumulated
+     * PowerStats, both stored in PowerStatsStore and not-yet processed.
+     */
+    public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            long monotonicStartTime, long monotonicEndTime) {
+        long maxEndTime = monotonicStartTime;
+        List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
+        for (int i = spans.size() - 1; i >= 0; i--) {
+            PowerStatsSpan.Metadata metadata = spans.get(i);
+            if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+                continue;
+            }
+
+            List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames();
+            long spanMinTime = Long.MAX_VALUE;
+            long spanMaxTime = Long.MIN_VALUE;
+            for (int j = 0; j < timeFrames.size(); j++) {
+                PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j);
+                long startMonotonicTime = timeFrame.startMonotonicTime;
+                long endMonotonicTime = startMonotonicTime + timeFrame.duration;
+                if (startMonotonicTime < spanMinTime) {
+                    spanMinTime = startMonotonicTime;
+                }
+                if (endMonotonicTime > spanMaxTime) {
+                    spanMaxTime = endMonotonicTime;
+                }
+            }
+
+            if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) {
+                continue;
+            }
+
+            if (spanMaxTime > maxEndTime) {
+                maxEndTime = spanMaxTime;
+            }
+
+            PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+                    AggregatedPowerStatsSection.TYPE);
+            if (span == null) {
+                Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+                continue;
+            }
+            List<PowerStatsSpan.Section> sections = span.getSections();
+            for (int k = 0; k < sections.size(); k++) {
+                PowerStatsSpan.Section section = sections.get(k);
+                populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+                        ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
+            }
+        }
+
+        if (maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
+            mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
+                    stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
+        }
+    }
+
+    private void populateBatteryUsageStatsBuilder(
+            BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats) {
+        AggregatedPowerStatsConfig config = mPowerStatsAggregator.getConfig();
+        List<AggregatedPowerStatsConfig.PowerComponent> powerComponents =
+                config.getPowerComponentsAggregatedStatsConfigs();
+        for (int i = powerComponents.size() - 1; i >= 0; i--) {
+            populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats,
+                    powerComponents.get(i));
+        }
+    }
+
+    private void populateBatteryUsageStatsBuilder(
+            BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats,
+            AggregatedPowerStatsConfig.PowerComponent powerComponent) {
+        int powerComponentId = powerComponent.getPowerComponentId();
+        PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(
+                powerComponentId);
+        if (powerComponentStats == null) {
+            return;
+        }
+
+        PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
+        if (descriptor == null) {
+            return;
+        }
+
+        PowerStatsCollector.StatsArrayLayout layout = new PowerStatsCollector.StatsArrayLayout();
+        layout.fromExtras(descriptor.extras);
+
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        double[] totalPower = new double[1];
+        MultiStateStats.States.forEachTrackedStateCombination(powerComponent.getDeviceStateConfig(),
+                states -> {
+                    if (states[AggregatedPowerStatsConfig.STATE_POWER]
+                            != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+                        return;
+                    }
+
+                    if (!powerComponentStats.getDeviceStats(deviceStats, states)) {
+                        return;
+                    }
+
+                    totalPower[0] += layout.getDevicePowerEstimate(deviceStats);
+                });
+
+        AggregateBatteryConsumer.Builder deviceScope =
+                batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        deviceScope.addConsumedPower(powerComponentId,
+                totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
+
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        ArrayList<Integer> uids = new ArrayList<>();
+        powerComponentStats.collectUids(uids);
+
+        boolean breakDownByProcState =
+                batteryUsageStatsBuilder.isProcessStateDataNeeded()
+                && powerComponent
+                        .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE]
+                        .isTracked();
+
+        double[] powerByProcState =
+                new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
+        double powerAllApps = 0;
+        for (int uid : uids) {
+            UidBatteryConsumer.Builder builder =
+                    batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
+
+            Arrays.fill(powerByProcState, 0);
+
+            MultiStateStats.States.forEachTrackedStateCombination(
+                    powerComponent.getUidStateConfig(),
+                    states -> {
+                        if (states[AggregatedPowerStatsConfig.STATE_POWER]
+                                != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) {
+                            return;
+                        }
+
+                        if (!powerComponentStats.getUidStats(uidStats, uid, states)) {
+                            return;
+                        }
+
+                        double power = layout.getUidPowerEstimate(uidStats);
+                        int procState = breakDownByProcState
+                                ? states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE]
+                                : BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+                        powerByProcState[procState] += power;
+                    });
+
+            double powerAllProcStates = 0;
+            for (int procState = 0; procState < powerByProcState.length; procState++) {
+                double power = powerByProcState[procState];
+                if (power == 0) {
+                    continue;
+                }
+                powerAllProcStates += power;
+                if (breakDownByProcState
+                        && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                    builder.addConsumedPower(builder.getKey(powerComponentId, procState),
+                            power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+                }
+            }
+            builder.addConsumedPower(powerComponentId, powerAllProcStates,
+                    BatteryConsumer.POWER_MODEL_UNDEFINED);
+            powerAllApps += powerAllProcStates;
+        }
+
+        AggregateBatteryConsumer.Builder allAppsScope =
+                batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+        allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
+                BatteryConsumer.POWER_MODEL_UNDEFINED);
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
index 551302e..97d872a 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -19,8 +19,6 @@
 import android.annotation.DurationMillisLong;
 import android.app.AlarmManager;
 import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.util.IndentingPrintWriter;
@@ -52,7 +50,6 @@
     private final MonotonicClock mMonotonicClock;
     private final Handler mHandler;
     private final BatteryStatsImpl mBatteryStats;
-    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
     private final PowerStatsAggregator mPowerStatsAggregator;
     private long mLastSavedSpanEndMonotonicTime;
 
@@ -60,7 +57,7 @@
             @DurationMillisLong long aggregatedPowerStatsSpanDuration,
             @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
             Clock clock, MonotonicClock monotonicClock, Handler handler,
-            BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+            BatteryStatsImpl batteryStats) {
         mContext = context;
         mPowerStatsAggregator = powerStatsAggregator;
         mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
@@ -70,16 +67,15 @@
         mMonotonicClock = monotonicClock;
         mHandler = handler;
         mBatteryStats = batteryStats;
-        mBatteryUsageStatsProvider = batteryUsageStatsProvider;
     }
 
     /**
      * Kicks off the scheduling of power stats aggregation spans.
      */
     public void start(boolean enablePeriodicPowerStatsCollection) {
-        mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
         mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
         if (mEnablePeriodicPowerStatsCollection) {
+            schedulePowerStatsAggregation();
             scheduleNextPowerStatsAggregation();
         }
     }
@@ -235,28 +231,6 @@
         mPowerStatsStore.storeAggregatedPowerStats(stats);
     }
 
-    private void storeBatteryUsageStatsOnReset(int resetReason) {
-        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
-            return;
-        }
-
-        final BatteryUsageStats batteryUsageStats =
-                mBatteryUsageStatsProvider.getBatteryUsageStats(
-                        new BatteryUsageStatsQuery.Builder()
-                                .setMaxStatsAgeMs(0)
-                                .includePowerModels()
-                                .includeProcessStateData()
-                                .build());
-
-        // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
-        // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
-        // start time
-        long monotonicStartTime =
-                mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
-        mHandler.post(() ->
-                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
-    }
-
     private void awaitCompletion() {
         ConditionVariable done = new ConditionVariable();
         mHandler.post(done::open);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java
new file mode 100644
index 0000000..8dc3609
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java
@@ -0,0 +1,241 @@
+/*
+ * 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.power.stats;
+
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Maintains a map of isolated UIDs to their respective owner UIDs, to support combining
+ * power stats for isolated UIDs, which are typically short-lived, into the corresponding app UID.
+ */
+public class PowerStatsUidResolver {
+    private static final String TAG = "PowerStatsUidResolver";
+
+    /**
+     * Listener notified when isolated UIDs are created and removed.
+     */
+    public interface Listener {
+
+        /**
+         * Callback invoked when a new isolated UID is registered.
+         */
+        void onIsolatedUidAdded(int isolatedUid, int parentUid);
+
+        /**
+         * Callback invoked before an isolated UID is evicted from the resolver.
+         * If the listener calls {@link PowerStatsUidResolver#retainIsolatedUid}, the mapping
+         * will be retained until {@link PowerStatsUidResolver#releaseIsolatedUid} is called.
+         */
+        void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid);
+
+        /**
+         * Callback invoked when an isolated UID to owner UID mapping is removed.
+         */
+        void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid);
+    }
+
+    /**
+     * Mapping isolated uids to the actual owning app uid.
+     */
+    private final SparseIntArray mIsolatedUids = new SparseIntArray();
+
+    /**
+     * Internal reference count of isolated uids.
+     */
+    private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+
+    // Keep the list read-only in order to avoid locking during the delivery of listener calls.
+    private volatile List<Listener> mListeners = Collections.emptyList();
+
+    /**
+     * Adds a listener.
+     */
+    public void addListener(Listener listener) {
+        synchronized (this) {
+            List<Listener> newList = new ArrayList<>(mListeners);
+            newList.add(listener);
+            mListeners = Collections.unmodifiableList(newList);
+        }
+    }
+
+    /**
+     * Removes a listener.
+     */
+    public void removeListener(Listener listener) {
+        synchronized (this) {
+            List<Listener> newList = new ArrayList<>(mListeners);
+            newList.remove(listener);
+            mListeners = Collections.unmodifiableList(newList);
+        }
+    }
+
+    /**
+     * Remembers the connection between a newly created isolated UID and its owner app UID.
+     * Calls {@link Listener#onIsolatedUidAdded} on each registered listener.
+     */
+    public void noteIsolatedUidAdded(int isolatedUid, int parentUid) {
+        synchronized (this) {
+            mIsolatedUids.put(isolatedUid, parentUid);
+            mIsolatedUidRefCounts.put(isolatedUid, 1);
+        }
+
+        List<Listener> listeners = mListeners;
+        for (int i = listeners.size() - 1; i >= 0; i--) {
+            listeners.get(i).onIsolatedUidAdded(isolatedUid, parentUid);
+        }
+    }
+
+    /**
+     * Handles the removal of an isolated UID by invoking
+     * {@link Listener#onBeforeIsolatedUidRemoved} on each registered listener and the releases
+     * the UID, see {@link #releaseIsolatedUid}.
+     */
+    public void noteIsolatedUidRemoved(int isolatedUid, int parentUid) {
+        synchronized (this) {
+            int curUid = mIsolatedUids.get(isolatedUid, -1);
+            if (curUid != parentUid) {
+                Slog.wtf(TAG, "Attempt to remove an isolated UID " + isolatedUid
+                              + " with the parent UID " + parentUid
+                              + ". The registered parent UID is " + curUid);
+                return;
+            }
+        }
+
+        List<Listener> listeners = mListeners;
+        for (int i = listeners.size() - 1; i >= 0; i--) {
+            listeners.get(i).onBeforeIsolatedUidRemoved(isolatedUid, parentUid);
+        }
+
+        releaseIsolatedUid(isolatedUid);
+    }
+
+    /**
+     * Increments the ref count for an isolated uid.
+     * Call #releaseIsolatedUid to decrement.
+     */
+    public void retainIsolatedUid(int uid) {
+        synchronized (this) {
+            final int refCount = mIsolatedUidRefCounts.get(uid, 0);
+            if (refCount <= 0) {
+                // Uid is not mapped or referenced
+                Slog.w(TAG,
+                        "Attempted to increment ref counted of untracked isolated uid (" + uid
+                        + ")");
+                return;
+            }
+            mIsolatedUidRefCounts.put(uid, refCount + 1);
+        }
+    }
+
+    /**
+     * Decrements the ref count for the given isolated UID.  If the ref count drops to zero,
+     * removes the mapping and calls {@link Listener#onAfterIsolatedUidRemoved} on each registered
+     * listener.
+     */
+    public void releaseIsolatedUid(int isolatedUid) {
+        int parentUid;
+        synchronized (this) {
+            final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
+            if (refCount > 0) {
+                // Isolated uid is still being tracked
+                mIsolatedUidRefCounts.put(isolatedUid, refCount);
+                return;
+            }
+
+            final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+            if (idx >= 0) {
+                parentUid = mIsolatedUids.valueAt(idx);
+                mIsolatedUids.removeAt(idx);
+                mIsolatedUidRefCounts.delete(isolatedUid);
+            } else {
+                Slog.w(TAG, "Attempted to remove untracked child uid (" + isolatedUid + ")");
+                return;
+            }
+        }
+
+        List<Listener> listeners = mListeners;
+        for (int i = listeners.size() - 1; i >= 0; i--) {
+            listeners.get(i).onAfterIsolatedUidRemoved(isolatedUid, parentUid);
+        }
+    }
+
+    /**
+     * Releases all isolated UIDs in the specified range, both ends inclusive.
+     */
+    public void releaseUidsInRange(int startUid, int endUid) {
+        IntArray toRelease;
+        synchronized (this) {
+            int startIndex = mIsolatedUids.indexOfKey(startUid);
+            int endIndex = mIsolatedUids.indexOfKey(endUid);
+
+            if (startIndex < 0) {
+                startIndex = ~startIndex;
+            }
+
+            if (endIndex < 0) {
+                // In this ~endIndex is pointing just past where endUid would be, so we must -1.
+                endIndex = ~endIndex - 1;
+            }
+
+            if (startIndex > endIndex) {
+                return;
+            }
+
+            toRelease = new IntArray(endIndex - startIndex);
+            for (int i = startIndex; i <= endIndex; i++) {
+                toRelease.add(mIsolatedUids.keyAt(i));
+            }
+        }
+
+        for (int i = toRelease.size() - 1; i >= 0; i--) {
+            releaseIsolatedUid(toRelease.get(i));
+        }
+    }
+
+    /**
+     * Given an isolated UID, returns the corresponding owner UID.  For a non-isolated
+     * UID, returns the UID itself.
+     */
+    public int mapUid(int uid) {
+        synchronized (this) {
+            return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid);
+        }
+    }
+
+    /**
+     * Dumps the current contents of the resolver for the sake of dumpsys.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Currently mapped isolated uids:");
+        synchronized (this) {
+            final int numIsolatedUids = mIsolatedUids.size();
+            for (int i = 0; i < numIsolatedUids; i++) {
+                final int isolatedUid = mIsolatedUids.keyAt(i);
+                final int ownerUid = mIsolatedUids.valueAt(i);
+                final int refs = mIsolatedUidRefCounts.get(isolatedUid);
+                pw.println("  " + isolatedUid + "->" + ownerUid + " (ref count = " + refs + ")");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c1da589..940feb5 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -24,12 +24,14 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
 import static android.hardware.graphics.common.Hdr.DOLBY_VISION;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkTemplate.MATCH_ETHERNET;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_PROXY;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
 import static android.net.NetworkTemplate.OEM_MANAGED_PAID;
@@ -488,6 +490,7 @@
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+                    case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG:
                     case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
                     case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
                     case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER:
@@ -973,21 +976,33 @@
         if (DEBUG) {
             Slog.d(TAG, "Registering NetworkStats pullers with statsd");
         }
+
+        boolean canQueryTypeProxy = canQueryNetworkStatsForTypeProxy();
+
         // Initialize NetworkStats baselines.
-        mNetworkStatsBaselines.addAll(
-                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
-        mNetworkStatsBaselines.addAll(
-                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
-        mNetworkStatsBaselines.addAll(
-                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
-        mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
-                FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
-        mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
-                FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
-        mNetworkStatsBaselines.addAll(
-                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
-        mNetworkStatsBaselines.addAll(
-                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
+        synchronized (mDataBytesTransferLock) {
+            mNetworkStatsBaselines.addAll(
+                    collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+            mNetworkStatsBaselines.addAll(
+                    collectNetworkStatsSnapshotForAtom(
+                            FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
+            mNetworkStatsBaselines.addAll(
+                    collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                    FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
+            mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                    FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
+            mNetworkStatsBaselines.addAll(
+                    collectNetworkStatsSnapshotForAtom(
+                            FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
+            mNetworkStatsBaselines.addAll(
+                    collectNetworkStatsSnapshotForAtom(
+                            FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
+            if (canQueryTypeProxy) {
+                mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                        FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG));
+            }
+        }
 
         // Listen to subscription changes to record historical subscriptions that activated before
         // pulling, this is used by {@code DATA_USAGE_BYTES_TRANSFER}.
@@ -1001,6 +1016,9 @@
         registerBytesTransferByTagAndMetered();
         registerDataUsageBytesTransfer();
         registerOemManagedBytesTransfer();
+        if (canQueryTypeProxy) {
+            registerProxyBytesTransferBackground();
+        }
     }
 
     private void initAndRegisterDeferredPullers() {
@@ -1171,6 +1189,18 @@
                 }
                 break;
             }
+            case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
+                final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
+                        new NetworkTemplate.Builder(MATCH_PROXY).build(),  /*includeTags=*/true);
+                if (stats != null) {
+                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+                            new int[]{TRANSPORT_BLUETOOTH},
+                            /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+                            /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                            /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
+                }
+                break;
+            }
             case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
                 final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
                         new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true);
@@ -1183,7 +1213,7 @@
                             new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
                             /*slicedByFgbg=*/false, /*slicedByTag=*/true,
                             /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                            /*subInfo=*/null, OEM_MANAGED_ALL));
+                            /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
                 }
                 break;
             }
@@ -1225,7 +1255,7 @@
             final NetworkStatsExt diff = new NetworkStatsExt(
                     removeEmptyEntries(item.stats.subtract(baseline.stats)), item.transports,
                     item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType,
-                    item.subInfo, item.oemManaged);
+                    item.subInfo, item.oemManaged, item.isTypeProxy);
 
             // If no diff, skip.
             if (!diff.stats.iterator().hasNext()) continue;
@@ -1363,7 +1393,7 @@
                     ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
                             new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
                             /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                            /*subInfo=*/null, oemManaged));
+                            /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
                 }
             }
         }
@@ -1392,6 +1422,21 @@
     }
 
     /**
+     * Check if it is possible to query NetworkStats for TYPE_PROXY. This should only be possible
+     * if the build includes r.android.com/2828315
+     * @return true if querying for TYPE_PROXY is allowed
+     */
+    private static boolean canQueryNetworkStatsForTypeProxy() {
+        try {
+            new NetworkTemplate.Builder(MATCH_PROXY).build();
+            return true;
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Querying network stats for TYPE_PROXY is not allowed");
+            return false;
+        }
+    }
+
+    /**
      * Create a snapshot of NetworkStats since boot for the given template, but add 1 bucket
      * duration before boot as a buffer to ensure at least one full bucket will be included.
      * Note that this should be only used to calculate diff since the snapshot might contains
@@ -1421,6 +1466,7 @@
 
         final NetworkStats nonTaggedStats =
                 NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
+        queryNonTaggedStats.close();
         if (!includeTags) return nonTaggedStats;
 
         final android.app.usage.NetworkStats queryTaggedStats =
@@ -1429,6 +1475,7 @@
                         currentTimeInMillis);
         final NetworkStats taggedStats =
                 NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats);
+        queryTaggedStats.close();
         return nonTaggedStats.add(taggedStats);
     }
 
@@ -1448,7 +1495,7 @@
                 ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
                         new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
                         /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
-                        OEM_MANAGED_ALL));
+                        OEM_MANAGED_ALL, /*isTypeProxy=*/false));
             }
         }
         return ret;
@@ -1598,6 +1645,19 @@
         );
     }
 
+    private void registerProxyBytesTransferBackground() {
+        int tagId = FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[]{3, 4, 5, 6})
+                .build();
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                metadata,
+                DIRECT_EXECUTOR,
+                mStatsCallbackImpl
+        );
+    }
+
     private void registerBytesTransferByTagAndMetered() {
         int tagId = FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED;
         PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
index 7dbba0d..512f0bf 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
@@ -42,15 +42,17 @@
     public final int oemManaged;
     @Nullable
     public final SubInfo subInfo;
+    public final boolean isTypeProxy; // True if matching ConnectivityManager#TYPE_PROXY
 
     public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
         this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, OEM_MANAGED_ALL);
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null,
+                OEM_MANAGED_ALL, /*isTypeProxy=*/false);
     }
 
     public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
             boolean slicedByTag, boolean slicedByMetered, int ratType,
-            @Nullable SubInfo subInfo, int oemManaged) {
+            @Nullable SubInfo subInfo, int oemManaged, boolean isTypeProxy) {
         this.stats = stats;
 
         // Sort transports array so that we can test for equality without considering order.
@@ -63,6 +65,7 @@
         this.ratType = ratType;
         this.subInfo = subInfo;
         this.oemManaged = oemManaged;
+        this.isTypeProxy = isTypeProxy;
     }
 
     /**
@@ -72,6 +75,6 @@
         return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
                 && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered
                 && ratType == other.ratType && Objects.equals(subInfo, other.subInfo)
-                && oemManaged == other.oemManaged;
+                && oemManaged == other.oemManaged && isTypeProxy == other.isTypeProxy;
     }
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3fd8323..7c51e7b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1836,12 +1836,13 @@
     }
 
     @Override
-    public void hideCurrentInputMethodForBubbles() {
+    public void hideCurrentInputMethodForBubbles(int displayId) {
         enforceStatusBarService();
         final long token = Binder.clearCallingIdentity();
         try {
-            InputMethodManagerInternal.get().hideCurrentInputMethod(
-                    SoftInputShowHideReason.HIDE_BUBBLES);
+            // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
+            InputMethodManagerInternal.get().hideAllInputMethods(
+                    SoftInputShowHideReason.HIDE_BUBBLES, displayId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING
index 5c37680..17d327e 100644
--- a/services/core/java/com/android/server/timedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING
@@ -7,10 +7,7 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
-    }
-  ],
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+    },
     {
       "name": "FrameworksTimeServicesTests"
     }
diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
index 63dd7b4..358618a 100644
--- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
+++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
@@ -7,15 +7,15 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "FrameworksTimeServicesTests"
     }
   ],
   // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
   "postsubmit": [
     {
       "name": "CtsLocationTimeZoneManagerHostTest"
-    },
-    {
-      "name": "FrameworksTimeServicesTests"
     }
   ]
 }
diff --git a/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java
new file mode 100644
index 0000000..e542349
--- /dev/null
+++ b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java
@@ -0,0 +1,194 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+import android.util.KeyValueListParser;
+
+/**
+ * Helper class to mediate the value to use when a constant exists in both a key=value pair Settings
+ * constant (that can be parsed by {@link KeyValueListParser})
+ * and the {@link DeviceConfig} properties.
+ */
+public abstract class UserSettingDeviceConfigMediator {
+    private static final String TAG = UserSettingDeviceConfigMediator.class.getSimpleName();
+
+    @Nullable
+    protected DeviceConfig.Properties mProperties;
+    @NonNull
+    protected final KeyValueListParser mSettingsParser;
+
+    /**
+     * @param keyValueListDelimiter The delimiter passed into the {@link KeyValueListParser}.
+     */
+    protected UserSettingDeviceConfigMediator(char keyValueListDelimiter) {
+        mSettingsParser = new KeyValueListParser(keyValueListDelimiter);
+    }
+
+    /**
+     * Sets the key=value list string to read from. Setting {@code null} will clear any previously
+     * set string.
+     */
+    public void setSettingsString(@Nullable String settings) {
+        mSettingsParser.setString(settings);
+    }
+
+    /**
+     * Sets the DeviceConfig Properties to read from. Setting {@code null} will clear any previously
+     * set properties.
+     */
+    public void setDeviceConfigProperties(@Nullable DeviceConfig.Properties properties) {
+        mProperties = properties;
+    }
+
+    /**
+     * Get the value for key as a boolean.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract boolean getBoolean(@NonNull String key, boolean defaultValue);
+
+    /**
+     * Get the value for key as a float.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract float getFloat(@NonNull String key, float defaultValue);
+
+    /**
+     * Get the value for key as an int.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract int getInt(@NonNull String key, int defaultValue);
+
+    /**
+     * Get the value for key as a long.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract long getLong(@NonNull String key, long defaultValue);
+
+    /**
+     * Get the value for key as a String.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract String getString(@NonNull String key, @Nullable String defaultValue);
+
+    /**
+     * A mediator in which the existence of a single settings key-value pair will override usage
+     * of DeviceConfig properties. That is, if the Settings constant has any values set,
+     * then everything in the DeviceConfig namespace will be ignored.
+     */
+    public static class SettingsOverridesAllMediator extends UserSettingDeviceConfigMediator {
+        public SettingsOverridesAllMediator(char keyValueListDelimiter) {
+            super(keyValueListDelimiter);
+        }
+
+        @Override
+        public boolean getBoolean(@NonNull String key, boolean defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null
+                        ? defaultValue : mProperties.getBoolean(key, defaultValue);
+            }
+            return mSettingsParser.getBoolean(key, defaultValue);
+        }
+
+        @Override
+        public float getFloat(@NonNull String key, float defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue);
+            }
+            return mSettingsParser.getFloat(key, defaultValue);
+        }
+
+        @Override
+        public int getInt(@NonNull String key, int defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue);
+            }
+            return mSettingsParser.getInt(key, defaultValue);
+        }
+
+        @Override
+        public long getLong(@NonNull String key, long defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue);
+            }
+            return mSettingsParser.getLong(key, defaultValue);
+        }
+
+        @Override
+        public String getString(@NonNull String key, @Nullable String defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null
+                        ? defaultValue : mProperties.getString(key, defaultValue);
+            }
+            return mSettingsParser.getString(key, defaultValue);
+        }
+    }
+
+    /**
+     * A mediator in which only individual keys in the DeviceConfig namespace will be overridden
+     * by the same key in the Settings constant. If the Settings constant does not have a specific
+     * key set, then the DeviceConfig value will be used instead.
+     */
+    public static class SettingsOverridesIndividualMediator
+            extends UserSettingDeviceConfigMediator {
+        public SettingsOverridesIndividualMediator(char keyValueListDelimiter) {
+            super(keyValueListDelimiter);
+        }
+
+        @Override
+        public boolean getBoolean(@NonNull String key, boolean defaultValue) {
+            return mSettingsParser.getBoolean(key,
+                    mProperties == null ? defaultValue : mProperties.getBoolean(key, defaultValue));
+        }
+
+        @Override
+        public float getFloat(@NonNull String key, float defaultValue) {
+            return mSettingsParser.getFloat(key,
+                    mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue));
+        }
+
+        @Override
+        public int getInt(@NonNull String key, int defaultValue) {
+            return mSettingsParser.getInt(key,
+                    mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue));
+        }
+
+        @Override
+        public long getLong(@NonNull String key, long defaultValue) {
+            return mSettingsParser.getLong(key,
+                    mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue));
+        }
+
+        @Override
+        public String getString(@NonNull String key, @Nullable String defaultValue) {
+            return mSettingsParser.getString(key,
+                    mProperties == null ? defaultValue : mProperties.getString(key, defaultValue));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f8078d2..e088d9a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -284,7 +284,6 @@
                         + " needsUpdate=" + needsUpdate);
             }
 
-            int notifyColorsWhich = 0;
             synchronized (mLock) {
                 notifyCallbacksLocked(wallpaper);
 
@@ -338,7 +337,6 @@
                     // If this was the system wallpaper, rebind...
                     bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
                             callback);
-                    notifyColorsWhich |= wallpaper.mWhich;
                 }
 
                 if (lockWallpaperChanged) {
@@ -358,7 +356,6 @@
 
                     bindWallpaperComponentLocked(mImageWallpaper, true /* force */,
                             false /* fromUser */, wallpaper, callback);
-                    notifyColorsWhich |= FLAG_LOCK;
                 } else if (isAppliedToLock) {
                     // This is system-plus-lock: we need to wipe the lock bookkeeping since
                     // we're falling back to displaying the system wallpaper there.
@@ -372,7 +369,6 @@
                     }
                     clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK);
                     mLockWallpaperMap.remove(wallpaper.userId);
-                    notifyColorsWhich |= FLAG_LOCK;
                 }
 
                 saveSettingsLocked(wallpaper.userId);
@@ -382,9 +378,7 @@
             }
 
             // Outside of the lock since it will synchronize itself
-            if (notifyColorsWhich != 0) {
-                notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
-            }
+            notifyWallpaperColorsChanged(wallpaper);
         }
 
         @Override
@@ -406,16 +400,13 @@
         }
     }
 
-    void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+    void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) {
         if (DEBUG) {
             Slog.i(TAG, "Notifying wallpaper colors changed");
         }
         if (wallpaper.connection != null) {
-            wallpaper.connection.forEachDisplayConnector(connector -> {
-                notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
-            });
-        } else { // Lock wallpaper does not have WallpaperConnection.
-            notifyWallpaperColorsChangedOnDisplay(wallpaper, which, DEFAULT_DISPLAY);
+            wallpaper.connection.forEachDisplayConnector(connector ->
+                    notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId));
         }
     }
 
@@ -430,7 +421,7 @@
         return listeners;
     }
 
-    private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int which,
+    private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
             int displayId) {
         boolean needsExtraction;
         synchronized (mLock) {
@@ -445,17 +436,20 @@
             }
 
             if (DEBUG) {
-                Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which);
+                Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + wallpaper.mWhich);
             }
 
             needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim;
         }
 
+        boolean notify = true;
         if (needsExtraction) {
-            extractColors(wallpaper);
+            notify = extractColors(wallpaper);
         }
-        notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
-                wallpaper.userId, displayId);
+        if (notify) {
+            notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper),
+                    wallpaper.mWhich, wallpaper.userId, displayId);
+        }
     }
 
     private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) {
@@ -505,8 +499,9 @@
      * In this case, using the crop is more than enough. Live wallpapers are just ignored.
      *
      * @param wallpaper a wallpaper representation
+     * @return true unless the wallpaper changed during the color computation
      */
-    private void extractColors(WallpaperData wallpaper) {
+    private boolean extractColors(WallpaperData wallpaper) {
         String cropFile = null;
         boolean defaultImageWallpaper = false;
         int wallpaperId;
@@ -518,13 +513,13 @@
 
         if (wallpaper.equals(mFallbackWallpaper)) {
             synchronized (mLock) {
-                if (mFallbackWallpaper.primaryColors != null) return;
+                if (mFallbackWallpaper.primaryColors != null) return true;
             }
             final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper);
             synchronized (mLock) {
                 mFallbackWallpaper.primaryColors = colors;
             }
-            return;
+            return true;
         }
 
         synchronized (mLock) {
@@ -554,7 +549,7 @@
 
         if (colors == null) {
             Slog.w(TAG, "Cannot extract colors because wallpaper could not be read.");
-            return;
+            return true;
         }
 
         synchronized (mLock) {
@@ -563,8 +558,10 @@
                 // Now that we have the colors, let's save them into the xml
                 // to avoid having to run this again.
                 saveSettingsLocked(wallpaper.userId);
+                return true;
             } else {
                 Slog.w(TAG, "Not setting primary colors since wallpaper changed");
+                return false;
             }
         }
     }
@@ -1138,19 +1135,15 @@
          */
         @Override
         public void onWallpaperColorsChanged(WallpaperColors primaryColors, int displayId) {
-            int which;
             synchronized (mLock) {
                 // Do not broadcast changes on ImageWallpaper since it's handled
                 // internally by this class.
                 if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
                     return;
                 }
-                which = mWallpaper.mWhich;
                 mWallpaper.primaryColors = primaryColors;
             }
-            if (which != 0) {
-                notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId);
-            }
+            notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId);
         }
 
         @Override
@@ -1794,9 +1787,9 @@
             // Offload color extraction to another thread since switchUser will be called
             // from the main thread.
             FgThread.getHandler().post(() -> {
-                notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM);
-                notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
-                notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
+                notifyWallpaperColorsChanged(systemWallpaper);
+                if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper);
+                notifyWallpaperColorsChanged(mFallbackWallpaper);
             });
         } finally {
             t.traceEnd();
@@ -1873,12 +1866,6 @@
                 data = mWallpaperMap.get(userId);
             }
         }
-
-        // When clearing a wallpaper, broadcast new valid colors
-        if (data != null) {
-            notifyWallpaperColorsChanged(data, which);
-            notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
-        }
     }
 
     private void clearWallpaperLocked(int which, int userId, boolean fromForeground,
@@ -2650,7 +2637,7 @@
                 }
             }
             for (WallpaperData wp: pendingColorExtraction) {
-                notifyWallpaperColorsChanged(wp, wp.mWhich);
+                notifyWallpaperColorsChanged(wp);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -3030,8 +3017,7 @@
         }
 
         if (shouldNotifyColors) {
-            notifyWallpaperColorsChanged(newWallpaper, which);
-            notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
+            notifyWallpaperColorsChanged(newWallpaper);
         }
         return bindSuccess;
     }
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 68f554c..ea8a801 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.Context;
@@ -237,18 +239,30 @@
 
     @Override
     public int getMultiProcessSetting(Context context) {
-        return Settings.Global.getInt(context.getContentResolver(),
-                                      Settings.Global.WEBVIEW_MULTIPROCESS, 0);
+        if (updateServiceV2()) {
+            throw new IllegalStateException(
+                    "getMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
+        }
+        return Settings.Global.getInt(
+                context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
     }
 
     @Override
     public void setMultiProcessSetting(Context context, int value) {
-        Settings.Global.putInt(context.getContentResolver(),
-                               Settings.Global.WEBVIEW_MULTIPROCESS, value);
+        if (updateServiceV2()) {
+            throw new IllegalStateException(
+                    "setMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
+        }
+        Settings.Global.putInt(
+                context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
     }
 
     @Override
     public void notifyZygote(boolean enableMultiProcess) {
+        if (updateServiceV2()) {
+            throw new IllegalStateException(
+                    "notifyZygote shouldn't be called if update_service_v2 flag is set.");
+        }
         WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
     }
 
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ee46ce1..e9c4096 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -51,7 +53,7 @@
     private static final String TAG = "WebViewUpdateService";
 
     private BroadcastReceiver mWebViewUpdatedReceiver;
-    private WebViewUpdateServiceImpl mImpl;
+    private WebViewUpdateServiceInterface mImpl;
 
     static final int PACKAGE_CHANGED = 0;
     static final int PACKAGE_ADDED = 1;
@@ -60,7 +62,11 @@
 
     public WebViewUpdateService(Context context) {
         super(context);
-        mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+        if (updateServiceV2()) {
+            mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+        } else {
+            mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+        }
     }
 
     @Override
@@ -151,8 +157,13 @@
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            (new WebViewUpdateServiceShellCommand(this)).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            if (updateServiceV2()) {
+                (new WebViewUpdateServiceShellCommand2(this))
+                        .exec(this, in, out, err, args, callback, resultReceiver);
+            } else {
+                (new WebViewUpdateServiceShellCommand(this))
+                        .exec(this, in, out, err, args, callback, resultReceiver);
+            }
         }
 
 
@@ -247,6 +258,11 @@
         }
 
         @Override // Binder call
+        public WebViewProviderInfo getDefaultWebViewPackage() {
+            return WebViewUpdateService.this.mImpl.getDefaultWebViewPackage();
+        }
+
+        @Override // Binder call
         public WebViewProviderInfo[] getAllWebViewPackages() {
             return WebViewUpdateService.this.mImpl.getWebViewPackages();
         }
@@ -269,18 +285,31 @@
 
         @Override // Binder call
         public boolean isMultiProcessEnabled() {
+            if (updateServiceV2()) {
+                throw new IllegalStateException(
+                        "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is"
+                                + " set.");
+            }
             return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
         }
 
         @Override // Binder call
         public void enableMultiProcess(boolean enable) {
-            if (getContext().checkCallingPermission(
-                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
+            if (updateServiceV2()) {
+                throw new IllegalStateException(
+                        "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
+            }
+            if (getContext()
+                            .checkCallingPermission(
+                                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
                     != PackageManager.PERMISSION_GRANTED) {
-                String msg = "Permission Denial: enableMultiProcess() from pid="
-                        + Binder.getCallingPid()
-                        + ", uid=" + Binder.getCallingUid()
-                        + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
+                String msg =
+                        "Permission Denial: enableMultiProcess() from pid="
+                                + Binder.getCallingPid()
+                                + ", uid="
+                                + Binder.getCallingUid()
+                                + " requires "
+                                + android.Manifest.permission.WRITE_SECURE_SETTINGS;
                 Slog.w(TAG, msg);
                 throw new SecurityException(msg);
             }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 43d62aa..60dc4ff 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -17,6 +17,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
@@ -29,8 +30,12 @@
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
+import com.android.server.LocalServices;
+import com.android.server.PinnerService;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -63,7 +68,7 @@
  *
  * @hide
  */
-class WebViewUpdateServiceImpl {
+class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
     private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
 
     private static class WebViewPackageMissingException extends Exception {
@@ -88,6 +93,8 @@
     private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
     private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
 
+    private static final String PIN_GROUP = "webview";
+
     private final SystemInterface mSystemInterface;
     private final Context mContext;
 
@@ -112,7 +119,8 @@
         mSystemInterface = systemInterface;
     }
 
-    void packageStateChanged(String packageName, int changedState, int userId) {
+    @Override
+    public void packageStateChanged(String packageName, int changedState, int userId) {
         // We don't early out here in different cases where we could potentially early-out (e.g. if
         // we receive PACKAGE_CHANGED for another user than the system user) since that would
         // complicate this logic further and open up for more edge cases.
@@ -163,7 +171,8 @@
         }
     }
 
-    void prepareWebViewInSystemServer() {
+    @Override
+    public void prepareWebViewInSystemServer() {
         mSystemInterface.notifyZygote(isMultiProcessEnabled());
         try {
             synchronized (mLock) {
@@ -210,7 +219,8 @@
         mSystemInterface.ensureZygoteStarted();
     }
 
-    void handleNewUser(int userId) {
+    @Override
+    public void handleNewUser(int userId) {
         // The system user is always started at boot, and by that point we have already run one
         // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
         // out here.
@@ -218,7 +228,8 @@
         handleUserChange();
     }
 
-    void handleUserRemoved(int userId) {
+    @Override
+    public void handleUserRemoved(int userId) {
         handleUserChange();
     }
 
@@ -232,14 +243,16 @@
         updateCurrentWebViewPackage(null);
     }
 
-    void notifyRelroCreationCompleted() {
+    @Override
+    public void notifyRelroCreationCompleted() {
         synchronized (mLock) {
             mNumRelroCreationsFinished++;
             checkIfRelrosDoneLocked();
         }
     }
 
-    WebViewProviderResponse waitForAndGetProvider() {
+    @Override
+    public WebViewProviderResponse waitForAndGetProvider() {
         PackageInfo webViewPackage = null;
         final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
         boolean webViewReady = false;
@@ -284,7 +297,8 @@
      * replacing that provider it will not be in use directly, but will be used when the relros
      * or the replacement are done).
      */
-    String changeProviderAndSetting(String newProviderName) {
+    @Override
+    public String changeProviderAndSetting(String newProviderName) {
         PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
         if (newPackage == null) return "";
         return newPackage.packageName;
@@ -332,6 +346,34 @@
         return newPackage;
     }
 
+    private void pinWebviewIfRequired(ApplicationInfo appInfo) {
+        PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+        int webviewPinQuota = pinnerService.getWebviewPinQuota();
+        if (webviewPinQuota <= 0) {
+            return;
+        }
+
+        pinnerService.unpinGroup(PIN_GROUP);
+
+        ArrayList<String> apksToPin = new ArrayList<>();
+        boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true);
+        for (String sharedLib : appInfo.sharedLibraryFiles) {
+            apksToPin.add(sharedLib);
+        }
+        apksToPin.add(appInfo.sourceDir);
+        if (!pinSharedFirst) {
+            // We want to prioritize pinning of the native library that is most likely used by apps
+            // which in some build flavors live in the main apk and as a shared library for others.
+            Collections.reverse(apksToPin);
+        }
+        for (String apk : apksToPin) {
+            if (webviewPinQuota <= 0) {
+                break;
+            }
+            int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP);
+            webviewPinQuota -= bytesPinned;
+        }
+    }
     /**
      * This is called when we change WebView provider, either when the current provider is
      * updated or a new provider is chosen / takes precedence.
@@ -340,6 +382,7 @@
         synchronized (mLock) {
             mAnyWebViewInstalled = true;
             if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+                pinWebviewIfRequired(newPackage.applicationInfo);
                 mCurrentWebViewPackage = newPackage;
 
                 // The relro creations might 'finish' (not start at all) before
@@ -367,7 +410,8 @@
     /**
      * Fetch only the currently valid WebView packages.
      **/
-    WebViewProviderInfo[] getValidWebViewPackages() {
+    @Override
+    public WebViewProviderInfo[] getValidWebViewPackages() {
         ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
         WebViewProviderInfo[] providers =
             new WebViewProviderInfo[providersAndPackageInfos.length];
@@ -377,6 +421,13 @@
         return providers;
     }
 
+    @Override
+    public WebViewProviderInfo getDefaultWebViewPackage() {
+        throw new IllegalStateException(
+                "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
+                        + " disabled.");
+    }
+
     private static class ProviderAndPackageInfo {
         public final WebViewProviderInfo provider;
         public final PackageInfo packageInfo;
@@ -464,11 +515,13 @@
         return true;
     }
 
-    WebViewProviderInfo[] getWebViewPackages() {
+    @Override
+    public WebViewProviderInfo[] getWebViewPackages() {
         return mSystemInterface.getWebViewPackages();
     }
 
-    PackageInfo getCurrentWebViewPackage() {
+    @Override
+    public PackageInfo getCurrentWebViewPackage() {
         synchronized (mLock) {
             return mCurrentWebViewPackage;
         }
@@ -620,7 +673,8 @@
         return null;
     }
 
-    boolean isMultiProcessEnabled() {
+    @Override
+    public boolean isMultiProcessEnabled() {
         int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
         if (mSystemInterface.isMultiProcessDefaultEnabled()) {
             // Multiprocess should be enabled unless the user has turned it off manually.
@@ -631,7 +685,8 @@
         }
     }
 
-    void enableMultiProcess(boolean enable) {
+    @Override
+    public void enableMultiProcess(boolean enable) {
         PackageInfo current = getCurrentWebViewPackage();
         mSystemInterface.setMultiProcessSetting(mContext,
                 enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
@@ -644,7 +699,8 @@
     /**
      * Dump the state of this Service.
      */
-    void dumpState(PrintWriter pw) {
+    @Override
+    public void dumpState(PrintWriter pw) {
         pw.println("Current WebView Update Service state");
         pw.println(String.format("  Multiprocess enabled: %b", isMultiProcessEnabled()));
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
new file mode 100644
index 0000000..29782d9
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2016 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.webkit;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.AsyncTask;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+import android.util.Slog;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the WebViewUpdateService.
+ * This class doesn't depend on the android system like the actual Service does and can be used
+ * directly by tests (as long as they implement a SystemInterface).
+ *
+ * This class keeps track of and prepares the current WebView implementation, and needs to keep
+ * track of a couple of different things such as what package is used as WebView implementation.
+ *
+ * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
+ * thread or on one of multiple Binder threads. The WebView preparation code shares state between
+ * threads meaning that code that chooses a new WebView implementation or checks which
+ * implementation is being used needs to hold a lock.
+ *
+ * The WebViewUpdateService can be accessed in a couple of different ways.
+ * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
+ * as the WebView preparation class.
+ * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
+ * and the WebViewUpdateService should not have been accessed before this call. In this call we
+ * choose WebView implementation for the first time.
+ * 3. The update service listens for Intents related to package installs and removals. These intents
+ * are received and processed on the UI thread. Each intent can result in changing WebView
+ * implementation.
+ * 4. The update service can be reached through Binder calls which are handled on specific binder
+ * threads. These calls can be made from any process. Generally they are used for changing WebView
+ * implementation (from Settings), getting information about the current WebView implementation (for
+ * loading WebView into an app process), or notifying the service about Relro creation being
+ * completed.
+ *
+ * @hide
+ */
+class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+    private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
+
+    private static class WebViewPackageMissingException extends Exception {
+        WebViewPackageMissingException(String message) {
+            super(message);
+        }
+    }
+
+    private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
+    private static final long NS_PER_MS = 1000000;
+
+    private static final int VALIDITY_OK = 0;
+    private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+    private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
+    private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
+    private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
+
+    private final SystemInterface mSystemInterface;
+    private final Context mContext;
+
+    private long mMinimumVersionCode = -1;
+
+    // Keeps track of the number of running relro creations
+    private int mNumRelroCreationsStarted = 0;
+    private int mNumRelroCreationsFinished = 0;
+    // Implies that we need to rerun relro creation because we are using an out-of-date package
+    private boolean mWebViewPackageDirty = false;
+    private boolean mAnyWebViewInstalled = false;
+
+    private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+    // The WebView package currently in use (or the one we are preparing).
+    private PackageInfo mCurrentWebViewPackage = null;
+
+    private final Object mLock = new Object();
+
+    WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
+        mContext = context;
+        mSystemInterface = systemInterface;
+    }
+
+    @Override
+    public void packageStateChanged(String packageName, int changedState, int userId) {
+        // We don't early out here in different cases where we could potentially early-out (e.g. if
+        // we receive PACKAGE_CHANGED for another user than the system user) since that would
+        // complicate this logic further and open up for more edge cases.
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            String webviewPackage = provider.packageName;
+
+            if (webviewPackage.equals(packageName)) {
+                boolean updateWebView = false;
+                boolean removedOrChangedOldPackage = false;
+                String oldProviderName = null;
+                PackageInfo newPackage = null;
+                synchronized (mLock) {
+                    try {
+                        newPackage = findPreferredWebViewPackage();
+                        if (mCurrentWebViewPackage != null) {
+                            oldProviderName = mCurrentWebViewPackage.packageName;
+                        }
+                        // Only trigger update actions if the updated package is the one
+                        // that will be used, or the one that was in use before the
+                        // update, or if we haven't seen a valid WebView package before.
+                        updateWebView =
+                            provider.packageName.equals(newPackage.packageName)
+                            || provider.packageName.equals(oldProviderName)
+                            || mCurrentWebViewPackage == null;
+                        // We removed the old package if we received an intent to remove
+                        // or replace the old package.
+                        removedOrChangedOldPackage =
+                            provider.packageName.equals(oldProviderName);
+                        if (updateWebView) {
+                            onWebViewProviderChanged(newPackage);
+                        }
+                    } catch (WebViewPackageMissingException e) {
+                        mCurrentWebViewPackage = null;
+                        Slog.e(TAG, "Could not find valid WebView package to create relro with "
+                                + e);
+                    }
+                }
+                if (updateWebView && !removedOrChangedOldPackage
+                        && oldProviderName != null) {
+                    // If the provider change is the result of adding or replacing a
+                    // package that was not the previous provider then we must kill
+                    // packages dependent on the old package ourselves. The framework
+                    // only kills dependents of packages that are being removed.
+                    mSystemInterface.killPackageDependents(oldProviderName);
+                }
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void prepareWebViewInSystemServer() {
+        try {
+            synchronized (mLock) {
+                mCurrentWebViewPackage = findPreferredWebViewPackage();
+                String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+                if (userSetting != null
+                        && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
+                    // Don't persist the user-chosen setting across boots if the package being
+                    // chosen is not used (could be disabled or uninstalled) so that the user won't
+                    // be surprised by the device switching to using a certain webview package,
+                    // that was uninstalled/disabled a long time ago, if it is installed/enabled
+                    // again.
+                    mSystemInterface.updateUserSetting(mContext,
+                            mCurrentWebViewPackage.packageName);
+                }
+                onWebViewProviderChanged(mCurrentWebViewPackage);
+            }
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Slog.e(TAG, "error preparing webview provider from system server", t);
+        }
+
+        if (getCurrentWebViewPackage() == null) {
+            // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+            // fallback package for all users in case it was disabled, even if we already did the
+            // one-time migration before. If this actually changes the state, we will see the
+            // PackageManager broadcast shortly and try again.
+            WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
+            WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
+            if (fallbackProvider != null) {
+                Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
+                mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
+                                                          true);
+            } else {
+                Slog.e(TAG, "No valid provider and no fallback available.");
+            }
+        }
+    }
+
+    private void startZygoteWhenReady() {
+        // Wait on a background thread for RELRO creation to be done. We ignore the return value
+        // because even if RELRO creation failed we still want to start the zygote.
+        waitForAndGetProvider();
+        mSystemInterface.ensureZygoteStarted();
+    }
+
+    @Override
+    public void handleNewUser(int userId) {
+        // The system user is always started at boot, and by that point we have already run one
+        // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
+        // out here.
+        if (userId == UserHandle.USER_SYSTEM) return;
+        handleUserChange();
+    }
+
+    @Override
+    public void handleUserRemoved(int userId) {
+        handleUserChange();
+    }
+
+    /**
+     * Called when a user was added or removed to ensure WebView preparation is triggered.
+     * This has to be done since the WebView package we use depends on the enabled-state
+     * of packages for all users (so adding or removing a user might cause us to change package).
+     */
+    private void handleUserChange() {
+        // Potentially trigger package-changing logic.
+        updateCurrentWebViewPackage(null);
+    }
+
+    @Override
+    public void notifyRelroCreationCompleted() {
+        synchronized (mLock) {
+            mNumRelroCreationsFinished++;
+            checkIfRelrosDoneLocked();
+        }
+    }
+
+    @Override
+    public WebViewProviderResponse waitForAndGetProvider() {
+        PackageInfo webViewPackage = null;
+        final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+        boolean webViewReady = false;
+        int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+        synchronized (mLock) {
+            webViewReady = webViewIsReadyLocked();
+            while (!webViewReady) {
+                final long timeNowMs = System.nanoTime() / NS_PER_MS;
+                if (timeNowMs >= timeoutTimeMs) break;
+                try {
+                    mLock.wait(timeoutTimeMs - timeNowMs);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+                webViewReady = webViewIsReadyLocked();
+            }
+            // Make sure we return the provider that was used to create the relro file
+            webViewPackage = mCurrentWebViewPackage;
+            if (webViewReady) {
+                // success
+            } else if (!mAnyWebViewInstalled) {
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+            } else {
+                // Either the current relro creation  isn't done yet, or the new relro creatioin
+                // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+                String timeoutError = "Timed out waiting for relro creation, relros started "
+                        + mNumRelroCreationsStarted
+                        + " relros finished " + mNumRelroCreationsFinished
+                        + " package dirty? " + mWebViewPackageDirty;
+                Slog.e(TAG, timeoutError);
+                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
+            }
+        }
+        if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+        return new WebViewProviderResponse(webViewPackage, webViewStatus);
+    }
+
+    /**
+     * Change WebView provider and provider setting and kill packages using the old provider.
+     * Return the new provider (in case we are in the middle of creating relro files, or
+     * replacing that provider it will not be in use directly, but will be used when the relros
+     * or the replacement are done).
+     */
+    @Override
+    public String changeProviderAndSetting(String newProviderName) {
+        PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+        if (newPackage == null) return "";
+        return newPackage.packageName;
+    }
+
+    /**
+     * Update the current WebView package.
+     * @param newProviderName the package to switch to, null if no package has been explicitly
+     * chosen.
+     */
+    private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
+        PackageInfo oldPackage = null;
+        PackageInfo newPackage = null;
+        boolean providerChanged = false;
+        synchronized (mLock) {
+            oldPackage = mCurrentWebViewPackage;
+
+            if (newProviderName != null) {
+                mSystemInterface.updateUserSetting(mContext, newProviderName);
+            }
+
+            try {
+                newPackage = findPreferredWebViewPackage();
+                providerChanged = (oldPackage == null)
+                        || !newPackage.packageName.equals(oldPackage.packageName);
+            } catch (WebViewPackageMissingException e) {
+                // If updated the Setting but don't have an installed WebView package, the
+                // Setting will be used when a package is available.
+                mCurrentWebViewPackage = null;
+                Slog.e(TAG, "Couldn't find WebView package to use " + e);
+                return null;
+            }
+            // Perform the provider change if we chose a new provider
+            if (providerChanged) {
+                onWebViewProviderChanged(newPackage);
+            }
+        }
+        // Kill apps using the old provider only if we changed provider
+        if (providerChanged && oldPackage != null) {
+            mSystemInterface.killPackageDependents(oldPackage.packageName);
+        }
+        // Return the new provider, this is not necessarily the one we were asked to switch to,
+        // but the persistent setting will now be pointing to the provider we were asked to
+        // switch to anyway.
+        return newPackage;
+    }
+
+    /**
+     * This is called when we change WebView provider, either when the current provider is
+     * updated or a new provider is chosen / takes precedence.
+     */
+    private void onWebViewProviderChanged(PackageInfo newPackage) {
+        synchronized (mLock) {
+            mAnyWebViewInstalled = true;
+            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+                mCurrentWebViewPackage = newPackage;
+
+                // The relro creations might 'finish' (not start at all) before
+                // WebViewFactory.onWebViewProviderChanged which means we might not know the
+                // number of started creations before they finish.
+                mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+                mNumRelroCreationsFinished = 0;
+                mNumRelroCreationsStarted =
+                    mSystemInterface.onWebViewProviderChanged(newPackage);
+                // If the relro creations finish before we know the number of started creations
+                // we will have to do any cleanup/notifying here.
+                checkIfRelrosDoneLocked();
+            } else {
+                mWebViewPackageDirty = true;
+            }
+        }
+
+        // Once we've notified the system that the provider has changed and started RELRO creation,
+        // try to restart the zygote so that it will be ready when apps use it.
+        AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+    }
+
+    /** Fetch only the currently valid WebView packages. */
+    @Override
+    public WebViewProviderInfo[] getValidWebViewPackages() {
+        ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+        WebViewProviderInfo[] providers =
+                new WebViewProviderInfo[providersAndPackageInfos.length];
+        for (int n = 0; n < providersAndPackageInfos.length; n++) {
+            providers[n] = providersAndPackageInfos[n].provider;
+        }
+        return providers;
+    }
+
+    /**
+     * Returns the default WebView provider which should be first availableByDefault option in the
+     * system config.
+     */
+    @Override
+    public WebViewProviderInfo getDefaultWebViewPackage() {
+        WebViewProviderInfo[] webviewProviders = getWebViewPackages();
+        for (WebViewProviderInfo provider : webviewProviders) {
+            if (provider.availableByDefault) {
+                return provider;
+            }
+        }
+        // This should be unreachable because the config parser enforces that there is at least one
+        // availableByDefault provider.
+        throw new AndroidRuntimeException("No available by default WebView Provider.");
+    }
+
+    private static class ProviderAndPackageInfo {
+        public final WebViewProviderInfo provider;
+        public final PackageInfo packageInfo;
+
+        ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+            this.provider = provider;
+            this.packageInfo = packageInfo;
+        }
+    }
+
+    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        List<ProviderAndPackageInfo> providers = new ArrayList<>();
+        for (int n = 0; n < allProviders.length; n++) {
+            try {
+                PackageInfo packageInfo =
+                        mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+                if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
+                    providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+                }
+            } catch (NameNotFoundException e) {
+                // Don't add non-existent packages
+            }
+        }
+        return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+    }
+
+    /**
+     * Returns either the package info of the WebView provider determined in the following way:
+     * If the user has chosen a provider then use that if it is valid,
+     * otherwise use the first package in the webview priority list that is valid.
+     *
+     */
+    private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+        // If the user has chosen provider, use that (if it's installed and enabled for all
+        // users).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // User did not choose, or the choice failed; use the most stable provider that is
+        // installed and enabled for all users, and available by default (not through
+        // user choice).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.availableByDefault) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // This should never happen during normal operation (only with modified system images).
+        mAnyWebViewInstalled = false;
+        throw new WebViewPackageMissingException("Could not find a loadable WebView package");
+    }
+
+    /**
+     * Return true iff {@param packageInfos} point to only installed and enabled packages.
+     * The given packages {@param packageInfos} should all be pointing to the same package, but each
+     * PackageInfo representing a different user's package.
+     */
+    private static boolean isInstalledAndEnabledForAllUsers(
+            List<UserPackage> userPackages) {
+        for (UserPackage userPackage : userPackages) {
+            if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public WebViewProviderInfo[] getWebViewPackages() {
+        return mSystemInterface.getWebViewPackages();
+    }
+
+    @Override
+    public PackageInfo getCurrentWebViewPackage() {
+        synchronized (mLock) {
+            return mCurrentWebViewPackage;
+        }
+    }
+
+    /**
+     * Returns whether WebView is ready and is not going to go through its preparation phase
+     * again directly.
+     */
+    private boolean webViewIsReadyLocked() {
+        return !mWebViewPackageDirty
+            && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+            // The current package might be replaced though we haven't received an intent
+            // declaring this yet, the following flag makes anyone loading WebView to wait in
+            // this case.
+            && mAnyWebViewInstalled;
+    }
+
+    private void checkIfRelrosDoneLocked() {
+        if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+            if (mWebViewPackageDirty) {
+                mWebViewPackageDirty = false;
+                // If we have changed provider since we started the relro creation we need to
+                // redo the whole process using the new package instead.
+                try {
+                    PackageInfo newPackage = findPreferredWebViewPackage();
+                    onWebViewProviderChanged(newPackage);
+                } catch (WebViewPackageMissingException e) {
+                    mCurrentWebViewPackage = null;
+                    // If we can't find any valid WebView package we are now in a state where
+                    // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
+                    // should simply wait until we receive an intent declaring a new package was
+                    // installed.
+                }
+            } else {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+        // Ensure the provider targets this framework release (or a later one).
+        if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
+            return VALIDITY_INCORRECT_SDK_VERSION;
+        }
+        if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
+                && !mSystemInterface.systemIsDebuggable()) {
+            // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
+            // minimum version code. This check is only enforced for user builds.
+            return VALIDITY_INCORRECT_VERSION_CODE;
+        }
+        if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
+            return VALIDITY_INCORRECT_SIGNATURE;
+        }
+        if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
+            return VALIDITY_NO_LIBRARY_FLAG;
+        }
+        return VALIDITY_OK;
+    }
+
+    /**
+     * Both versionCodes should be from a WebView provider package implemented by Chromium.
+     * VersionCodes from other kinds of packages won't make any sense in this method.
+     *
+     * An introduction to Chromium versionCode scheme:
+     * "BBBBPPPXX"
+     * BBBB: 4 digit branch number. It monotonically increases over time.
+     * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
+     * may change their meaning in the future.
+     * XX: Digits to differentiate different APK builds of the same source version.
+     *
+     * This method takes the "BBBB" of versionCodes and compare them.
+     *
+     * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
+     * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
+     * is the canonical source for how Chromium versionCodes are calculated.
+     *
+     * @return true if versionCode1 is higher than or equal to versionCode2.
+     */
+    private static boolean versionCodeGE(long versionCode1, long versionCode2) {
+        long v1 = versionCode1 / 100000;
+        long v2 = versionCode2 / 100000;
+
+        return v1 >= v2;
+    }
+
+    /**
+     * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+     * of all available-by-default WebView provider packages. If there is no such WebView provider
+     * package on the system, then return -1, which means all positive versionCode WebView packages
+     * are accepted.
+     *
+     * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
+     * shared between threads. Furthermore, this method does not hold mLock meaning that we must
+     * take extra care to ensure this method is thread-safe.
+     */
+    private long getMinimumVersionCode() {
+        if (mMinimumVersionCode > 0) {
+            return mMinimumVersionCode;
+        }
+
+        long minimumVersionCode = -1;
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            if (provider.availableByDefault) {
+                try {
+                    long versionCode =
+                            mSystemInterface.getFactoryPackageVersion(provider.packageName);
+                    if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
+                        minimumVersionCode = versionCode;
+                    }
+                } catch (NameNotFoundException e) {
+                    // Safe to ignore.
+                }
+            }
+        }
+
+        mMinimumVersionCode = minimumVersionCode;
+        return mMinimumVersionCode;
+    }
+
+    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+            PackageInfo packageInfo, SystemInterface systemInterface) {
+        // Skip checking signatures on debuggable builds, for development purposes.
+        if (systemInterface.systemIsDebuggable()) return true;
+
+        // Allow system apps to be valid providers regardless of signature.
+        if (packageInfo.applicationInfo.isSystemApp()) return true;
+
+        // We don't support packages with multiple signatures.
+        if (packageInfo.signatures.length != 1) return false;
+
+        // If any of the declared signatures match the package signature, it's valid.
+        for (Signature signature : provider.signatures) {
+            if (signature.equals(packageInfo.signatures[0])) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the only fallback provider in the set of given packages, or null if there is none.
+     */
+    private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
+        for (WebViewProviderInfo provider : webviewPackages) {
+            if (provider.isFallback) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isMultiProcessEnabled() {
+        throw new IllegalStateException(
+                "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is set.");
+    }
+
+    @Override
+    public void enableMultiProcess(boolean enable) {
+        throw new IllegalStateException(
+                "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
+    }
+
+    /** Dump the state of this Service. */
+    @Override
+    public void dumpState(PrintWriter pw) {
+        pw.println("Current WebView Update Service state");
+        synchronized (mLock) {
+            if (mCurrentWebViewPackage == null) {
+                pw.println("  Current WebView package is null");
+            } else {
+                pw.println(
+                        TextUtils.formatSimple(
+                                "  Current WebView package (name, version): (%s, %s)",
+                                mCurrentWebViewPackage.packageName,
+                                mCurrentWebViewPackage.versionName));
+            }
+            pw.println(
+                    TextUtils.formatSimple(
+                            "  Minimum targetSdkVersion: %d", UserPackage.MINIMUM_SUPPORTED_SDK));
+            pw.println(
+                    TextUtils.formatSimple(
+                            "  Minimum WebView version code: %d", mMinimumVersionCode));
+            pw.println(
+                    TextUtils.formatSimple(
+                            "  Number of relros started: %d", mNumRelroCreationsStarted));
+            pw.println(
+                    TextUtils.formatSimple(
+                            "  Number of relros finished: %d", mNumRelroCreationsFinished));
+            pw.println(TextUtils.formatSimple("  WebView package dirty: %b", mWebViewPackageDirty));
+            pw.println(
+                    TextUtils.formatSimple(
+                            "  Any WebView package installed: %b", mAnyWebViewInstalled));
+
+            try {
+                PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
+                pw.println(
+                        TextUtils.formatSimple(
+                                "  Preferred WebView package (name, version): (%s, %s)",
+                                preferredWebViewPackage.packageName,
+                                preferredWebViewPackage.versionName));
+            } catch (WebViewPackageMissingException e) {
+                pw.println("  Preferred WebView package: none");
+            }
+
+            dumpAllPackageInformationLocked(pw);
+        }
+    }
+
+    private void dumpAllPackageInformationLocked(PrintWriter pw) {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        pw.println("  WebView packages:");
+        for (WebViewProviderInfo provider : allProviders) {
+            List<UserPackage> userPackages =
+                    mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+            PackageInfo systemUserPackageInfo =
+                    userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
+            if (systemUserPackageInfo == null) {
+                pw.println(
+                        TextUtils.formatSimple("    %s is NOT installed.", provider.packageName));
+                continue;
+            }
+
+            int validity = validityResult(provider, systemUserPackageInfo);
+            String packageDetails =
+                    TextUtils.formatSimple(
+                            "versionName: %s, versionCode: %d, targetSdkVersion: %d",
+                            systemUserPackageInfo.versionName,
+                            systemUserPackageInfo.getLongVersionCode(),
+                            systemUserPackageInfo.applicationInfo.targetSdkVersion);
+            if (validity == VALIDITY_OK) {
+                boolean installedForAllUsers =
+                        isInstalledAndEnabledForAllUsers(
+                                mSystemInterface.getPackageInfoForProviderAllUsers(
+                                        mContext, provider));
+                pw.println(
+                        TextUtils.formatSimple(
+                                "    Valid package %s (%s) is %s installed/enabled for all users",
+                                systemUserPackageInfo.packageName,
+                                packageDetails,
+                                installedForAllUsers ? "" : "NOT"));
+            } else {
+                pw.println(
+                        TextUtils.formatSimple(
+                                "    Invalid package %s (%s), reason: %s",
+                                systemUserPackageInfo.packageName,
+                                packageDetails,
+                                getInvalidityReason(validity)));
+            }
+        }
+    }
+
+    private static String getInvalidityReason(int invalidityReason) {
+        switch (invalidityReason) {
+            case VALIDITY_INCORRECT_SDK_VERSION:
+                return "SDK version too low";
+            case VALIDITY_INCORRECT_VERSION_CODE:
+                return "Version code too low";
+            case VALIDITY_INCORRECT_SIGNATURE:
+                return "Incorrect signature";
+            case VALIDITY_NO_LIBRARY_FLAG:
+                return "No WebView-library manifest flag";
+            default:
+                return "Unexcepted validity-reason";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
new file mode 100644
index 0000000..1772ef9
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
@@ -0,0 +1,52 @@
+/*
+ * 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.webkit;
+
+import android.content.pm.PackageInfo;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+
+interface WebViewUpdateServiceInterface {
+    void packageStateChanged(String packageName, int changedState, int userId);
+
+    void handleNewUser(int userId);
+
+    void handleUserRemoved(int userId);
+
+    WebViewProviderInfo[] getWebViewPackages();
+
+    void prepareWebViewInSystemServer();
+
+    void notifyRelroCreationCompleted();
+
+    WebViewProviderResponse waitForAndGetProvider();
+
+    String changeProviderAndSetting(String newProviderName);
+
+    WebViewProviderInfo[] getValidWebViewPackages();
+
+    WebViewProviderInfo getDefaultWebViewPackage();
+
+    PackageInfo getCurrentWebViewPackage();
+
+    boolean isMultiProcessEnabled();
+
+    void enableMultiProcess(boolean enable);
+
+    void dumpState(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java
new file mode 100644
index 0000000..ce95b18
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.webkit;
+
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.text.TextUtils;
+import android.webkit.IWebViewUpdateService;
+
+import java.io.PrintWriter;
+
+class WebViewUpdateServiceShellCommand2 extends ShellCommand {
+    final IWebViewUpdateService mInterface;
+
+    WebViewUpdateServiceShellCommand2(IWebViewUpdateService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd) {
+                case "set-webview-implementation":
+                    return setWebViewImplementation();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int setWebViewImplementation() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        String shellChosenPackage = getNextArg();
+        if (shellChosenPackage == null) {
+            pw.println("Failed to switch, no PACKAGE provided.");
+            pw.println("");
+            helpSetWebViewImplementation();
+            return 1;
+        }
+        String newPackage = mInterface.changeProviderAndSetting(shellChosenPackage);
+        if (shellChosenPackage.equals(newPackage)) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println(
+                    TextUtils.formatSimple(
+                            "Failed to switch to %s, the WebView implementation is now provided by"
+                                    + " %s.",
+                            shellChosenPackage, newPackage));
+            return 1;
+        }
+    }
+
+    public void helpSetWebViewImplementation() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("  set-webview-implementation PACKAGE");
+        pw.println("    Set the WebView implementation to the specified package.");
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("WebView updater commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        helpSetWebViewImplementation();
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
new file mode 100644
index 0000000..1411acc
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+    name: "update_service_v2"
+    namespace: "webview"
+    description: "Using a new version of the WebView update service"
+    bug: "308907090"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7b399c8..315e7d8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -55,6 +55,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import static com.android.window.flags.Flags.allowDisableActivityRecordInputSink;
 
 import android.Manifest;
 import android.annotation.ColorInt;
@@ -70,7 +71,6 @@
 import android.app.PictureInPictureParams;
 import android.app.PictureInPictureUiState;
 import android.app.compat.CompatChanges;
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.EnterPipRequestedItem;
 import android.app.servertransaction.PipStateTransactionItem;
 import android.compat.annotation.ChangeId;
@@ -1018,7 +1018,7 @@
         }
 
         try {
-            mService.getLifecycleManager().scheduleTransaction(r.app.getThread(),
+            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
                     EnterPipRequestedItem.obtain(r.token));
             return true;
         } catch (Exception e) {
@@ -1038,9 +1038,8 @@
         }
 
         try {
-            final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread());
-            transaction.addCallback(PipStateTransactionItem.obtain(r.token, pipState));
-            mService.getLifecycleManager().scheduleTransaction(transaction);
+            mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
+                    PipStateTransactionItem.obtain(r.token, pipState));
         } catch (Exception e) {
             Slog.w(TAG, "Failed to send pip state transaction item: "
                     + r.intent.getComponent(), e);
@@ -1690,4 +1689,20 @@
             return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken;
         }
     }
+
+    @Override
+    public void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+        if (!allowDisableActivityRecordInputSink()) {
+            return;
+        }
+
+        mService.mAmInternal.enforceCallingPermission(
+                Manifest.permission.INTERNAL_SYSTEM_WINDOW, "setActivityRecordInputSinkEnabled");
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
+            if (r != null) {
+                r.mActivityRecordInputSinkEnabled = enabled;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 24d9938..75e6faf 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -115,7 +115,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
-import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -277,7 +276,6 @@
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.MoveToDisplayItem;
@@ -696,7 +694,7 @@
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
-    private boolean mLastSurfaceShowing;
+    boolean mLastSurfaceShowing;
 
     /**
      * The activity is opaque and fills the entire space of this task.
@@ -972,6 +970,8 @@
     boolean mWaitForEnteringPinnedMode;
 
     final ActivityRecordInputSink mActivityRecordInputSink;
+    // System activities with INTERNAL_SYSTEM_WINDOW can disable ActivityRecordInputSink.
+    boolean mActivityRecordInputSinkEnabled = true;
 
     // Activities with this uid are allowed to not create an input sink while being in the same
     // task and directly above this ActivityRecord. This field is updated whenever a new activity
@@ -1449,7 +1449,7 @@
                     + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
                     config);
 
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     MoveToDisplayItem.obtain(token, displayId, config));
         } catch (RemoteException e) {
             // If process died, whatever.
@@ -1466,7 +1466,7 @@
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
                     + "config: %s", this, config);
 
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     ActivityConfigurationChangeItem.obtain(token, config));
         } catch (RemoteException e) {
             // If process died, whatever.
@@ -1487,7 +1487,7 @@
             ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
                     this, onTop);
 
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     TopResumedActivityChangeItem.obtain(token, onTop));
         } catch (RemoteException e) {
             // If process died, whatever.
@@ -1556,9 +1556,15 @@
             return false;
         }
 
-        // Transition change for the activity moving into a TaskFragment of different bounds.
-        return newParent.isOrganizedTaskFragment()
-                && !newParent.getBounds().equals(oldParent.getBounds());
+        final boolean isInPip2 = ActivityTaskManagerService.isPip2ExperimentEnabled()
+                && inPinnedWindowingMode();
+        if (!newParent.isOrganizedTaskFragment() && !isInPip2) {
+            // Parent TaskFragment isn't associated with a TF organizer and we are not in PiP2,
+            // so do not allow for initializeChangeTransition() on parent changes
+            return false;
+        }
+        // Transition change for the activity moving into TaskFragment of different bounds.
+        return !newParent.getBounds().equals(oldParent.getBounds());
     }
 
     @Override
@@ -2559,7 +2565,7 @@
                     }
                 }
                 if (abort) {
-                    surface.remove(false /* prepareAnimation */);
+                    surface.remove(false /* prepareAnimation */, false /* hasImeSurface */);
                 }
             } else {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",
@@ -2725,7 +2731,7 @@
      * Receive the splash screen data from shell, sending to client.
      * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy.
      */
-    void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
+    void onCopySplashScreenFinish(@Nullable SplashScreenViewParcelable parcelable) {
         removeTransferSplashScreenTimeout();
         final SurfaceControl windowAnimationLeash = (parcelable == null
                 || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
@@ -2744,7 +2750,7 @@
         }
         try {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     TransferSplashScreenViewStateItem.obtain(token, parcelable,
                             windowAnimationLeash));
             scheduleTransferSplashScreenTimeout();
@@ -2892,6 +2898,7 @@
 
         final StartingSurfaceController.StartingSurface surface;
         final boolean animate;
+        final boolean hasImeSurface;
         if (mStartingData != null) {
             if (mStartingData.mWaitForSyncTransactionCommit
                     || mTransitionController.isCollecting(this)) {
@@ -2901,6 +2908,7 @@
             }
             animate = prepareAnimation && mStartingData.needRevealAnimation()
                     && mStartingWindow.isVisibleByPolicy();
+            hasImeSurface = mStartingData.hasImeSurface();
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
                             + " animate=%b Callers=%s", this, mStartingWindow, animate,
                     Debug.getCallers(5));
@@ -2920,7 +2928,7 @@
                     this);
             return;
         }
-        surface.remove(animate);
+        surface.remove(animate, hasImeSurface);
     }
 
     /**
@@ -3908,7 +3916,7 @@
 
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         DestroyActivityItem.obtain(token, finishing, configChangeFlags));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process has crashed, our death
@@ -4819,7 +4827,7 @@
             try {
                 final ArrayList<ResultInfo> list = new ArrayList<>();
                 list.add(new ResultInfo(resultWho, requestCode, resultCode, data));
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         ActivityResultItem.obtain(token, list));
                 return;
             } catch (Exception e) {
@@ -4830,22 +4838,23 @@
         // Schedule sending results now for Media Projection setup.
         if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
                 STOPPING, STOPPED)) {
-            final ClientTransaction transaction = ClientTransaction.obtain(app.getThread());
             // Build result to be returned immediately.
-            transaction.addCallback(ActivityResultItem.obtain(
-                    token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
+            final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+                    token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data)));
             // When the activity result is delivered, the activity will transition to RESUMED.
             // Since the activity is only resumed so the result can be immediately delivered,
             // return it to its original lifecycle state.
-            ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
-            if (lifecycleItem != null) {
-                transaction.setLifecycleStateRequest(lifecycleItem);
-            } else {
-                Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
-                        + " so couldn't immediately send result");
-            }
+            final ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
             try {
-                mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+                if (lifecycleItem != null) {
+                    mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                            app.getThread(), activityResultItem, lifecycleItem);
+                } else {
+                    Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
+                            + " so couldn't immediately send result");
+                    mAtmService.getLifecycleManager().scheduleTransactionItem(
+                            app.getThread(), activityResultItem);
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending result to " + this, e);
             }
@@ -4925,7 +4934,7 @@
                 // Making sure the client state is RESUMED after transaction completed and doing
                 // so only if activity is currently RESUMED. Otherwise, client may have extra
                 // life-cycle calls to RESUMED (and PAUSED later).
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         NewIntentItem.obtain(token, ar, mState == RESUMED));
                 unsent = false;
             } catch (RemoteException e) {
@@ -5373,11 +5382,13 @@
                 // Finish should only ever commit visibility=false, so we can check full containment
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
-                if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
+                if (!inFinishingTransition) {
                     if (visible) {
-                        mTransitionController.onVisibleWithoutCollectingTransition(this,
-                                Debug.getCallers(1, 1));
-                    } else {
+                        if (!mDisplayContent.isSleeping() || canShowWhenLocked()) {
+                            mTransitionController.onVisibleWithoutCollectingTransition(this,
+                                    Debug.getCallers(1, 1));
+                        }
+                    } else if (!mDisplayContent.isSleeping()) {
                         Slog.w(TAG, "Set invisible without transition " + this);
                     }
                 }
@@ -5693,14 +5704,10 @@
         // can be synchronized with showing the next surface in the transition.
         if (!usingShellTransitions && !isVisible() && !delayed
                 && !displayContent.mAppTransition.isTransitionSet()) {
-            SurfaceControl.openTransaction();
-            try {
-                forAllWindows(win -> {
-                    win.mWinAnimator.hide(getGlobalTransaction(), "immediately hidden");
-                }, true);
-            } finally {
-                SurfaceControl.closeTransaction();
-            }
+            forAllWindows(win -> {
+                win.mWinAnimator.hide(getPendingTransaction(), "immediately hidden");
+            }, true);
+            scheduleAnimation();
         }
     }
 
@@ -6150,7 +6157,7 @@
             EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this),
                     shortComponentName, "userLeaving=false", "make-active");
             try {
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
                                 configChangeFlags, false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
@@ -6163,7 +6170,7 @@
             setState(STARTED, "makeActiveIfNeeded");
 
             try {
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+                mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         StartActivityItem.obtain(token, takeOptions()));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
@@ -6431,20 +6438,22 @@
 
     void stopIfPossible() {
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
-        final Task rootTask = getRootTask();
+        if (finishing) {
+            Slog.e(TAG, "Request to stop a finishing activity: " + this);
+            destroyIfPossible("stopIfPossible-finishing");
+            return;
+        }
         if (isNoHistory()) {
-            if (!finishing) {
-                if (!rootTask.shouldSleepActivities()) {
-                    ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this);
-                    if (finishIfPossible("stop-no-history", false /* oomAdj */)
-                            != FINISH_RESULT_CANCELLED) {
-                        resumeKeyDispatchingLocked();
-                        return;
-                    }
-                } else {
-                    ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop "
-                            + "because we're just sleeping", this);
+            if (!task.shouldSleepActivities()) {
+                ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this);
+                if (finishIfPossible("stop-no-history", false /* oomAdj */)
+                        != FINISH_RESULT_CANCELLED) {
+                    resumeKeyDispatchingLocked();
+                    return;
                 }
+            } else {
+                ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop "
+                        + "because we're just sleeping", this);
             }
         }
 
@@ -6461,7 +6470,7 @@
             }
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     StopActivityItem.obtain(token, configChangeFlags));
 
             mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
@@ -9901,10 +9910,8 @@
             } else {
                 lifecycleItem = PauseActivityItem.obtain(token);
             }
-            final ClientTransaction transaction = ClientTransaction.obtain(app.getThread());
-            transaction.addCallback(callbackItem);
-            transaction.setLifecycleStateRequest(lifecycleItem);
-            mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+            mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                    app.getThread(), callbackItem, lifecycleItem);
             startRelaunching();
             // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
             // request resume if this activity is currently resumed, which implies we aren't
@@ -9996,7 +10003,7 @@
         // The process will be killed until the activity reports stopped with saved state (see
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                     StopActivityItem.obtain(token, 0 /* configChanges */));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index be7d9b6..1a19787 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -86,10 +86,13 @@
         final boolean allowPassthrough = activityBelowInTask != null && (
                 activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
                         || activityBelowInTask.isUid(mActivityRecord.getUid()));
-        if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) {
+        if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()
+                || !mActivityRecord.mActivityRecordInputSinkEnabled) {
+            // Set to non-touchable, so the touch events can pass through.
             mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
                     InputConfig.NOT_TOUCHABLE);
         } else {
+            // Set to touchable, so it can block by intercepting the touch events.
             mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
         }
         mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 009b8e0..d6302e0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -103,6 +103,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -128,11 +129,13 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
+import com.android.server.pm.PackageArchiver;
 import com.android.server.power.ShutdownCheckPoints;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 import com.android.server.wm.BackgroundActivityStartController.BalCode;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
 
@@ -957,6 +960,17 @@
             }
         }
 
+        if (Flags.archiving()) {
+            PackageArchiver packageArchiver = mService
+                    .getPackageManagerInternalLocked()
+                    .getPackageArchiver();
+            if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
+                return packageArchiver
+                        .requestUnarchiveOnActivityStart(
+                                intent, callingPackage, mRequest.userId, realCallingUid);
+            }
+        }
+
         final int launchFlags = intent.getFlags();
         if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
             // Transfer the result target from the source activity to the new one being started,
@@ -1090,14 +1104,14 @@
         ActivityOptions checkedOptions = options != null
                 ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
 
-        @BalCode int balCode = BAL_ALLOW_DEFAULT;
+        final BalVerdict balVerdict;
         if (!abort) {
             try {
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
                         "shouldAbortBackgroundActivityStart");
                 BackgroundActivityStartController balController =
                         mSupervisor.getBackgroundActivityLaunchController();
-                BackgroundActivityStartController.BalVerdict balVerdict =
+                balVerdict =
                         balController.checkBackgroundActivityStart(
                             callingUid,
                             callingPid,
@@ -1109,13 +1123,13 @@
                             request.forcedBalByPiSender,
                             intent,
                             checkedOptions);
-                balCode = balVerdict.getCode();
-                request.logMessage.append(" (").append(
-                                BackgroundActivityStartController.balCodeToString(balCode))
-                        .append(")");
+                request.logMessage.append(" (").append(balVerdict).append(")");
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
+        } else {
+            // Sets ALLOW_BY_DEFAULT as default value as the activity launch will be aborted anyway.
+            balVerdict = BalVerdict.ALLOW_BY_DEFAULT;
         }
 
         if (request.allowPendingRemoteAnimationRegistryLookup) {
@@ -1293,13 +1307,13 @@
         WindowProcessController homeProcess = mService.mHomeProcess;
         boolean isHomeProcess = homeProcess != null
                 && aInfo.applicationInfo.uid == homeProcess.mUid;
-        if (balCode != BAL_BLOCK && !isHomeProcess) {
+        if (balVerdict.allows() && !isHomeProcess) {
             mService.resumeAppSwitches();
         }
 
         mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                 request.voiceInteractor, startFlags, checkedOptions,
-                inTask, inTaskFragment, balCode, intentGrants, realCallingUid);
+                inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid);
 
         if (request.outActivity != null) {
             request.outActivity[0] = mLastStartActivityRecord;
@@ -1449,7 +1463,8 @@
     private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, ActivityOptions options, Task inTask,
-            TaskFragment inTaskFragment, @BalCode int balCode,
+            TaskFragment inTaskFragment,
+            BalVerdict balVerdict,
             NeededUriGrants intentGrants, int realCallingUid) {
         int result = START_CANCELED;
         final Task startedActivityRootTask;
@@ -1468,7 +1483,7 @@
             try {
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
                 result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
-                        startFlags, options, inTask, inTaskFragment, balCode,
+                        startFlags, options, inTask, inTaskFragment, balVerdict,
                         intentGrants, realCallingUid);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -1615,10 +1630,10 @@
     int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, ActivityOptions options, Task inTask,
-            TaskFragment inTaskFragment, @BalCode int balCode,
+            TaskFragment inTaskFragment, BalVerdict balVerdict,
             NeededUriGrants intentGrants, int realCallingUid) {
         setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord,
-                voiceSession, voiceInteractor, balCode, realCallingUid);
+                voiceSession, voiceInteractor, balVerdict.getCode(), realCallingUid);
 
         computeLaunchingTaskFlags();
         mIntent.setFlags(mLaunchFlags);
@@ -1696,7 +1711,8 @@
             }
             recordTransientLaunchIfNeeded(targetTaskTop);
             // Recycle the target task for this launch.
-            startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
+            startResult =
+                    recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict);
             if (startResult != START_SUCCESS) {
                 return startResult;
             }
@@ -1730,6 +1746,7 @@
         recordTransientLaunchIfNeeded(mLastStartActivityRecord);
 
         if (!mAvoidMoveToFront && mDoResume) {
+            logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask);
             mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
             if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
                     && !dreamStopping) {
@@ -1800,6 +1817,7 @@
                 // now update the focused root-task accordingly.
                 if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
                         && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
+                    logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask);
                     mTargetRootTask.moveToFront("startActivityInner");
                 }
                 mRootWindowContainer.resumeFocusedTasksTopActivities(
@@ -1817,7 +1835,7 @@
         // Note that mStartActivity and source should be in the same Task at this point.
         if (mOptions != null && mOptions.isLaunchIntoPip()
                 && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
-                && balCode != BAL_BLOCK) {
+                && balVerdict.allows()) {
             mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
                     sourceRecord, "launch-into-pip");
         }
@@ -1828,6 +1846,24 @@
         return START_SUCCESS;
     }
 
+    private void logOnlyCreatorAllowsBAL(BalVerdict balVerdict,
+            int realCallingUid, boolean newTask) {
+        // TODO (b/296478675) eventually, we will prevent such case from happening
+        // and probably also log that a BAL is prevented by android V.
+        if (!newTask && balVerdict.onlyCreatorAllows()) {
+            String realCallingPackage =
+                    mService.mContext.getPackageManager().getNameForUid(realCallingUid);
+            if (realCallingPackage == null) {
+                realCallingPackage = "uid=" + realCallingUid;
+            }
+            Slog.wtf(TAG, "A background app is brought to the foreground due to a "
+                    + "PendingIntent. However, only the creator of the PendingIntent allows BAL, "
+                    + "while the sender does not allow BAL. realCallingPackage: "
+                    + realCallingPackage + "; callingPackage: " + mRequest.callingPackage
+                    + "; mTargetRootTask:" + mTargetRootTask);
+        }
+    }
+
     private void recordTransientLaunchIfNeeded(ActivityRecord r) {
         if (r == null || !mTransientLaunch) return;
         final TransitionController controller = r.mTransitionController;
@@ -1995,7 +2031,7 @@
      */
     @VisibleForTesting
     int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask,
-            NeededUriGrants intentGrants) {
+            NeededUriGrants intentGrants, BalVerdict balVerdict) {
         // Should not recycle task which is from a different user, just adding the starting
         // activity to the task.
         if (targetTask.mUserId != mStartActivity.mUserId) {
@@ -2024,7 +2060,7 @@
         mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */,
                 targetTaskTop);
 
-        setTargetRootTaskIfNeeded(targetTaskTop);
+        setTargetRootTaskIfNeeded(targetTaskTop, balVerdict);
 
         // When there is a reused activity and the current result is a trampoline activity,
         // set the reused activity as the result.
@@ -2040,6 +2076,7 @@
             if (!mMovedToFront && mDoResume) {
                 ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask,
                         targetTaskTop);
+                logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
                 mTargetRootTask.moveToFront("intentActivityFound");
             }
             resumeTargetRootTaskIfNeeded();
@@ -2068,6 +2105,7 @@
             targetTaskTop.showStartingWindow(true /* taskSwitch */);
         } else if (mDoResume) {
             // Make sure the root task and its belonging display are moved to topmost.
+            logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
             mTargetRootTask.moveToFront("intentActivityFound");
         }
         // We didn't do anything...  but it was needed (a.k.a., client don't use that intent!)
@@ -2663,7 +2701,7 @@
      * @param intentActivity Existing matching activity.
      * @return {@link ActivityRecord} brought to front.
      */
-    private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) {
+    private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity, BalVerdict balVerdict) {
         intentActivity.getTaskFragment().clearLastPausedActivity();
         Task intentTask = intentActivity.getTask();
         // The intent task might be reparented while in getOrCreateRootTask, caches the original
@@ -2730,6 +2768,7 @@
                     // task on top there.
                     // Defer resuming the top activity while moving task to top, since the
                     // current task-top activity may not be the activity that should be resumed.
+                    logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false);
                     mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
                             mStartActivity.appTimeTracker, DEFER_RESUME,
                             "bringingFoundTaskToFront");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3c56a4e..6f5c676 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -125,6 +125,7 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.WindowManagerService.MY_PID;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -165,6 +166,7 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.compat.CompatChanges;
+import android.app.sdksandbox.sandboxactivity.SdkSandboxActivityAuthority;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -279,6 +281,7 @@
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.wm.shell.Flags;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -314,8 +317,6 @@
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
     private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
-    private static final String ENABLE_PIP2_IMPLEMENTATION =
-            "persist.wm.debug.enable_pip2_implementation";
     static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
 
@@ -414,6 +415,8 @@
     boolean mHasCompanionDeviceSetupFeature;
     /** The process of the top most activity. */
     volatile WindowProcessController mTopApp;
+    /** The process showing UI while the device is dozing. */
+    volatile WindowProcessController mVisibleDozeUiProcess;
     /**
      * This is the process holding the activity the user last visited that is in a different process
      * from the one they are currently in.
@@ -1258,6 +1261,13 @@
                 true /*validateIncomingUser*/);
     }
 
+    static boolean isSdkSandboxActivity(Context context, Intent intent) {
+        return intent != null
+                && (sandboxActivitySdkBasedContext()
+                        ? SdkSandboxActivityAuthority.isSdkSandboxActivity(context, intent)
+                        : intent.isSandboxActivity(context));
+    }
+
     private int startActivityAsUser(IApplicationThread caller, String callingPackage,
             @Nullable String callingFeatureId, Intent intent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode, int startFlags,
@@ -1268,7 +1278,7 @@
         assertPackageMatchesCallingUid(callingPackage);
         enforceNotIsolatedCaller("startActivityAsUser");
 
-        if (intent != null && intent.isSandboxActivity(mContext)) {
+        if (isSdkSandboxActivity(mContext, intent)) {
             SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
                     SdkSandboxManagerLocal.class);
             sdkSandboxManagerLocal.enforceAllowedToHostSandboxedActivity(
@@ -3608,8 +3618,9 @@
      * @hide
      */
     @Override
-    public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
-            throws RemoteException {
+    public void onSplashScreenViewCopyFinished(int taskId,
+            @Nullable SplashScreenViewParcelable parcelable)
+                throws RemoteException {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
                 "copySplashScreenViewFinish()");
         synchronized (mGlobalLock) {
@@ -7251,6 +7262,6 @@
     }
 
     static boolean isPip2ExperimentEnabled() {
-        return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+        return Flags.enablePip2Implementation();
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e196d46..a21b9b4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -55,6 +55,7 @@
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
@@ -98,7 +99,6 @@
 import android.app.TaskInfo;
 import android.app.WaitResult;
 import android.app.servertransaction.ActivityLifecycleItem;
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.LaunchActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
@@ -928,14 +928,10 @@
                 }
 
                 // Create activity launch transaction.
-                final ClientTransaction clientTransaction = ClientTransaction.obtain(
-                        proc.getThread());
-
                 final boolean isTransitionForward = r.isTransitionForward();
                 final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
-
                 final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
-                clientTransaction.addCallback(LaunchActivityItem.obtain(r.token,
+                final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
                         r.intent, System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
@@ -945,7 +941,7 @@
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                         results, newIntents, r.takeOptions(), isTransitionForward,
                         proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
-                        r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
+                        r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
@@ -955,10 +951,10 @@
                 } else {
                     lifecycleItem = PauseActivityItem.obtain(r.token);
                 }
-                clientTransaction.setLifecycleStateRequest(lifecycleItem);
 
                 // Schedule transaction.
-                mService.getLifecycleManager().scheduleTransaction(clientTransaction);
+                mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                        proc.getThread(), launchActivityItem, lifecycleItem);
 
                 if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
                     // If the seq is increased, there should be something changed (e.g. registered
@@ -1089,7 +1085,7 @@
             // Remove the process record so it won't be considered as alive.
             mService.mProcessNames.remove(wpc.mName, wpc.mUid);
             mService.mProcessMap.remove(wpc.getPid());
-        } else if (r.intent.isSandboxActivity(mService.mContext)) {
+        } else if (ActivityTaskManagerService.isSdkSandboxActivity(mService.mContext, r.intent)) {
             Slog.e(TAG, "Abort sandbox activity launching as no sandbox process to host it.");
             r.finishIfPossible("No sandbox process for the activity", false /* oomAdj */);
             r.launchFailed = true;
@@ -1633,7 +1629,12 @@
         }
     }
 
-    private void removeRootTaskInSurfaceTransaction(Task rootTask) {
+    /**
+     * Removes the root task associated with the given {@param rootTask}. If the {@param rootTask}
+     * is the pinned task, then its child tasks are not explicitly removed when the root task is
+     * destroyed, but instead moved back onto the TaskDisplayArea.
+     */
+    void removeRootTask(Task rootTask) {
         if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) {
             removePinnedRootTaskInSurfaceTransaction(rootTask);
         } else {
@@ -1644,15 +1645,6 @@
     }
 
     /**
-     * Removes the root task associated with the given {@param task}. If the {@param task} is the
-     * pinned task, then its child tasks are not explicitly removed when the root task is
-     * destroyed, but instead moved back onto the TaskDisplayArea.
-     */
-    void removeRootTask(Task task) {
-        mWindowManager.inSurfaceTransaction(() -> removeRootTaskInSurfaceTransaction(task));
-    }
-
-    /**
      * Removes the task with the specified task id.
      *
      * @param taskId Identifier of the task to be removed.
@@ -1690,7 +1682,7 @@
             ArrayList<ActivityRecord> activities = null;
             for (int i = mStoppingActivities.size() - 1; i >= 0; i--) {
                 final ActivityRecord r = mStoppingActivities.get(i);
-                if (r.getTask() == task) {
+                if (!r.finishing && r.isState(RESUMED) && r.getTask() == task) {
                     if (activities == null) {
                         activities = new ArrayList<>();
                     }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 805bff2..05087f8 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -71,7 +71,6 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -82,7 +81,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
-import android.util.Slog;
 import android.view.Display;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
@@ -1179,16 +1177,7 @@
             }
             app.updateReportedVisibilityLocked();
             app.waitingToShow = false;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    ">>> OPEN TRANSACTION handleAppTransitionReady()");
-            mService.openSurfaceTransaction();
-            try {
-                app.showAllWindowsLocked();
-            } finally {
-                mService.closeSurfaceTransaction("handleAppTransitionReady");
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION handleAppTransitionReady()");
-            }
+            app.showAllWindowsLocked();
 
             if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
                 app.attachThumbnailAnimation();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 43f3209..be7b855 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -429,9 +429,12 @@
             final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
             if (prevTFAdjacent != null) {
                 if (prevTFAdjacent == currTF) {
-                    // Cannot predict what will happen when app receive back key, skip animation.
                     outPrevActivities.clear();
-                    return false;
+                    // No more activity in task, so it can predict if previous task exists.
+                    // Otherwise, unable to predict what will happen when app receive
+                    // back key, skip animation.
+                    return currentTask.getActivity((below) -> !below.finishing, prevActivity,
+                            false /*includeBoundary*/, true /*traverseTopToBottom*/) == null;
                 } else {
                     final ActivityRecord prevActivityAdjacent =
                             prevTFAdjacent.getTopNonFinishingActivity();
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index c2b5f88..eafaf8c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -29,6 +29,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balShowToasts;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
@@ -42,9 +43,13 @@
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -79,6 +84,11 @@
     private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
     private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
     private static final int NO_PROCESS_UID = -1;
+    /** If enabled the creator will not allow BAL on its behalf by default. */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
+            296478951;
     public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
             ActivityOptions.makeBasic()
                     .setPendingIntentBackgroundActivityStartMode(
@@ -264,12 +274,9 @@
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
             } else {
-                // for PendingIntents we restrict creator BAL based on target_sdk
-                mBalAllowedByPiCreator =
-                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
-                                ? BackgroundStartPrivileges.NONE
-                                : BackgroundStartPrivileges.ALLOW_BAL;
+                // for PendingIntents we restrict BAL based on target_sdk
+                mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator(
+                        callingUid, callingPackage, checkedOptions);
             }
             mBalAllowedByPiSender =
                     PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
@@ -303,6 +310,41 @@
             }
         }
 
+        private BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCreator(
+                int callingUid, String callingPackage, ActivityOptions checkedOptions) {
+            switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
+                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                    return BackgroundStartPrivileges.ALLOW_BAL;
+                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED:
+                    return BackgroundStartPrivileges.NONE;
+                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+                    // no explicit choice by the app - let us decide what to do
+                    if (!balRequireOptInByPendingIntentCreator()) {
+                        // if feature is disabled allow
+                        return BackgroundStartPrivileges.ALLOW_BAL;
+                    }
+                    if (callingPackage != null) {
+                        // determine based on the calling/creating package
+                        boolean changeEnabled = CompatChanges.isChangeEnabled(
+                                DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR,
+                                callingPackage,
+                                UserHandle.getUserHandleForUid(callingUid));
+                        return changeEnabled ? BackgroundStartPrivileges.NONE
+                                : BackgroundStartPrivileges.ALLOW_BAL;
+                    }
+                    // determine based on the calling/creating uid if we cannot determine the
+                    // actual package name (e.g. shared uid)
+                    boolean changeEnabled = CompatChanges.isChangeEnabled(
+                            DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR,
+                            callingUid);
+                    return changeEnabled ? BackgroundStartPrivileges.NONE
+                            : BackgroundStartPrivileges.ALLOW_BAL;
+                default:
+                    throw new IllegalStateException("unsupported BackgroundActivityStartMode: "
+                            + checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode());
+            }
+        }
+
         private String getDebugPackageName(String packageName, int uid) {
             if (packageName != null) {
                 return packageName; // use actual package
@@ -322,7 +364,7 @@
         }
 
         private boolean isPendingIntent() {
-            return mOriginatingPendingIntent != null;
+            return mOriginatingPendingIntent != null && hasRealCaller();
         }
 
         private String dump(BalVerdict resultIfPiCreatorAllowsBal) {
@@ -341,18 +383,23 @@
                     .append(getDebugPackageName(mCallingPackage, mCallingUid));
             sb.append("; callingUid: ").append(mCallingUid);
             sb.append("; callingPid: ").append(mCallingPid);
-            sb.append("; isPendingIntent: ").append(isPendingIntent());
             sb.append("; appSwitchState: ").append(mAppSwitchState);
             sb.append("; callingUidHasAnyVisibleWindow: ").append(mCallingUidHasAnyVisibleWindow);
             sb.append("; callingUidProcState: ").append(DebugUtils.valueToString(
                     ActivityManager.class, "PROCESS_STATE_", mCallingUidProcState));
             sb.append("; isCallingUidPersistentSystemProcess: ")
                     .append(mIsCallingUidPersistentSystemProcess);
+            sb.append("; forcedBalByPiSender: ").append(mForcedBalByPiSender);
+            sb.append("; intent: ").append(mIntent);
+            sb.append("; callerApp: ").append(mCallerApp);
+            if (mCallerApp != null) {
+                sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
+            }
             sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
+            sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isPendingIntent: ").append(isPendingIntent());
             if (hasRealCaller()) {
-                sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
                 sb.append("; realCallingPackage: ")
                         .append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
                 sb.append("; realCallingUid: ").append(mRealCallingUid);
@@ -364,24 +411,14 @@
                 sb.append("; isRealCallingUidPersistentSystemProcess: ")
                         .append(mIsRealCallingUidPersistentSystemProcess);
                 sb.append("; originatingPendingIntent: ").append(mOriginatingPendingIntent);
-            }
-            sb.append("; mForcedBalByPiSender: ").append(mForcedBalByPiSender);
-            sb.append("; intent: ").append(mIntent);
-            sb.append("; callerApp: ").append(mCallerApp);
-            if (hasRealCaller()) {
                 sb.append("; realCallerApp: ").append(mRealCallerApp);
-            }
-            if (mCallerApp != null) {
-                sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
-            }
-            if (hasRealCaller()) {
                 if (mRealCallerApp != null) {
                     sb.append("; realInVisibleTask: ")
                             .append(mRealCallerApp.hasActivityInVisibleTask());
                 }
+                sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
                 sb.append("; resultIfPiSenderAllowsBal: ").append(resultIfPiSenderAllowsBal);
             }
-            sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
             sb.append("]");
             return sb.toString();
         }
@@ -390,10 +427,15 @@
     static class BalVerdict {
 
         static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+        static final BalVerdict ALLOW_BY_DEFAULT =
+                new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default");
         private final @BalCode int mCode;
         private final boolean mBackground;
         private final String mMessage;
         private String mProcessInfo;
+        // indicates BAL would be blocked because only creator of the PI has the privilege to allow
+        // BAL, the sender does not have the privilege to allow BAL.
+        private boolean mOnlyCreatorAllows;
 
         BalVerdict(@BalCode int balCode, boolean background, String message) {
             this.mBackground = background;
@@ -414,12 +456,20 @@
             return !blocks();
         }
 
+        BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+            mOnlyCreatorAllows = onlyCreatorAllows;
+            return this;
+        }
+
+        boolean onlyCreatorAllows() {
+            return mOnlyCreatorAllows;
+        }
+
         public String toString() {
             StringBuilder builder = new StringBuilder();
-            builder.append(". BAL Code: ");
             builder.append(balCodeToString(mCode));
             if (DEBUG_ACTIVITY_STARTS) {
-                builder.append(" ");
+                builder.append(" (");
                 if (mBackground) {
                     builder.append("Background ");
                 }
@@ -433,6 +483,7 @@
                     builder.append(" ");
                     builder.append(mProcessInfo);
                 }
+                builder.append(")");
             }
             return builder.toString();
         }
@@ -502,18 +553,15 @@
         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));
+                            + state.dump(resultForCaller, resultForRealCaller));
                 }
                 return statsLog(resultForCaller, state);
             }
-            // anything that has fallen through would currently be aborted
-            Slog.w(TAG, "Background activity launch blocked! "
-                    + state.dump(resultForCaller));
-            showBalBlockedToast("BAL blocked", state);
-            return statsLog(BalVerdict.BLOCK, state);
+            return abortLaunch(state, resultForCaller, resultForRealCaller);
         }
 
         // The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -526,6 +574,10 @@
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
                 : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+        if (state.isPendingIntent()) {
+            resultForCaller.setOnlyCreatorAllows(
+                    resultForCaller.allows() && resultForRealCaller.blocks());
+        }
 
         if (resultForCaller.allows()
                 && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
@@ -545,37 +597,48 @@
             }
             return statsLog(resultForRealCaller, state);
         }
-        if (resultForCaller.allows() && resultForRealCaller.allows()
+        boolean callerCanAllow = resultForCaller.allows()
                 && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        boolean realCallerCanAllow = resultForRealCaller.allows()
                 && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        if (callerCanAllow && realCallerCanAllow) {
             // Both caller and real caller allow with system defined behavior
+            if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
+                Slog.wtf(TAG,
+                        "With Android 15 BAL hardening this activity start may be blocked"
+                                + " 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);
+                // return the realCaller result for backwards compatibility
+                return statsLog(resultForRealCaller, state);
+            }
             Slog.wtf(TAG,
-                    "With Android 15 BAL hardening this activity start may be blocked"
-                            + " if the PI creator upgrades target_sdk to 35+"
-                            + " AND the PI sender upgrades target_sdk to 34+! "
-                            + " (missing opt in by PI creator)! "
+                    "Without Android 15 BAL hardening this activity start would be allowed"
+                            + " (missing opt in by PI creator or sender)! "
                             + state.dump(resultForCaller, resultForRealCaller));
-            showBalRiskToast("BAL would be blocked", state);
-            // return the realCaller result for backwards compatibility
-            return statsLog(resultForRealCaller, state);
+            return abortLaunch(state, resultForCaller, resultForRealCaller);
         }
-        if (resultForCaller.allows()
-                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+        if (callerCanAllow) {
             // Allowed before V by creator
+            if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
+                Slog.wtf(TAG,
+                        "With Android 15 BAL hardening this activity start may be blocked"
+                                + " 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);
+                return statsLog(resultForCaller, state);
+            }
             Slog.wtf(TAG,
-                    "With Android 15 BAL hardening this activity start may be blocked"
-                            + " if the PI creator upgrades target_sdk to 35+! "
+                    "Without Android 15 BAL hardening this activity start would be allowed"
                             + " (missing opt in by PI creator)! "
                             + state.dump(resultForCaller, resultForRealCaller));
-            showBalRiskToast("BAL would be blocked", state);
-            return statsLog(resultForCaller, state);
+            return abortLaunch(state, resultForCaller, resultForRealCaller);
         }
-        if (resultForRealCaller.allows()
-                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+        if (realCallerCanAllow) {
             // Allowed before U by sender
             if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
                 Slog.wtf(TAG,
@@ -589,9 +652,14 @@
             Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
                     + " (missing opt in by PI sender)! "
                     + state.dump(resultForCaller, resultForRealCaller));
-            // fall through
+            return abortLaunch(state, resultForCaller, resultForRealCaller);
         }
-        // anything that has fallen through would currently be aborted
+        // neither the caller not the realCaller can allow or have explicitly opted out
+        return abortLaunch(state, resultForCaller, resultForRealCaller);
+    }
+
+    private BalVerdict abortLaunch(BalState state, BalVerdict resultForCaller,
+            BalVerdict resultForRealCaller) {
         Slog.w(TAG, "Background activity launch blocked! "
                 + state.dump(resultForCaller, resultForRealCaller));
         showBalBlockedToast("BAL blocked", state);
@@ -648,17 +716,18 @@
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
         final boolean appSwitchAllowedOrFg =
                 appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
-        final boolean allowCallingUidStartActivity =
-                ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
-                        && callingUidHasAnyVisibleWindow)
-                        || isCallingUidPersistentSystemProcess;
-        if (allowCallingUidStartActivity) {
+        if (appSwitchAllowedOrFg && callingUidHasAnyVisibleWindow) {
             return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false,
-                    "callingUidHasAnyVisibleWindow = "
-                            + callingUid
-                            + ", isCallingUidPersistentSystemProcess = "
-                            + isCallingUidPersistentSystemProcess);
+                    /*background*/ false, "callingUid has visible window");
+        }
+        if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) {
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                    /*background*/ false, "callingUid has non-app visible window");
+        }
+
+        if (isCallingUidPersistentSystemProcess) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ false, "callingUid is persistent system process");
         }
 
         // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index ef31837..8b282dd3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -40,35 +40,70 @@
      * @throws RemoteException
      *
      * @see ClientTransaction
+     * @deprecated use {@link #scheduleTransactionItem(IApplicationThread, ClientTransactionItem)}.
      */
+    @Deprecated
     void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException {
         final IApplicationThread client = transaction.getClient();
-        transaction.schedule();
-        if (!(client instanceof Binder)) {
-            // If client is not an instance of Binder - it's a remote call and at this point it is
-            // safe to recycle the object. All objects used for local calls will be recycled after
-            // the transaction is executed on client in ActivityThread.
-            transaction.recycle();
+        try {
+            transaction.schedule();
+        } finally {
+            if (!(client instanceof Binder)) {
+                // If client is not an instance of Binder - it's a remote call and at this point it
+                // is safe to recycle the object. All objects used for local calls will be recycled
+                // after the transaction is executed on client in ActivityThread.
+                transaction.recycle();
+            }
         }
     }
 
     /**
+     * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+     *
+     * @see WindowProcessController#setReportedProcState(int)
+     */
+    void scheduleTransactionItemUnlocked(@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);
+        } else {
+            clientTransaction.addCallback(transactionItem);
+        }
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
      * Schedules a single transaction item, either a callback or a lifecycle request, delivery to
      * client application.
-     * @param client Target client.
-     * @param transactionItem A transaction item to deliver a message.
      * @throws RemoteException
-     *
      * @see ClientTransactionItem
      */
-    void scheduleTransaction(@NonNull IApplicationThread client,
+    void scheduleTransactionItem(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem) throws RemoteException {
+        // TODO(b/260873529): queue the transaction items.
         final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
-        if (transactionItem instanceof ActivityLifecycleItem) {
+        if (transactionItem.isActivityLifecycleItem()) {
             clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
         } else {
             clientTransaction.addCallback(transactionItem);
         }
         scheduleTransaction(clientTransaction);
     }
+
+    /**
+     * Schedules a single transaction item with a lifecycle request, delivery to client application.
+     * @throws RemoteException
+     * @see ClientTransactionItem
+     */
+    void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
+            @NonNull ClientTransactionItem transactionItem,
+            @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+        // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+        clientTransaction.addCallback(transactionItem);
+        clientTransaction.setLifecycleStateRequest(lifecycleItem);
+        scheduleTransaction(clientTransaction);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 2fabb0e..7ce9de4 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -83,6 +83,7 @@
     /**
      * Mark all dims as pending completion on the next call to {@link #updateDims}
      *
+     * Called before iterating on mHost's children, first step of dimming.
      * This is intended for us by the host container, to be called at the beginning of
      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
@@ -100,8 +101,7 @@
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
-     * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates}
-     * should be set before calling this method.
+     * described in {@link #resetDimStates}.
      *
      * @param t      A transaction in which to update the dims.
      * @return true if any Dims were updated.
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
new file mode 100644
index 0000000..e91857f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -0,0 +1,334 @@
+/*
+ * 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.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+/**
+ * Contains the information relative to the changes to apply to the dim layer
+ */
+public class DimmerAnimationHelper {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationHelper" : TAG_WM;
+    private static final int DEFAULT_DIM_ANIM_DURATION_MS = 200;
+
+    /**
+     * Contains the requested changes
+     */
+    static class Change {
+        private float mAlpha = -1f;
+        private int mBlurRadius = -1;
+        private WindowContainer mDimmingContainer = null;
+        private int mRelativeLayer = -1;
+        private static final float EPSILON = 0.0001f;
+
+        Change() {}
+
+        Change(Change other) {
+            mAlpha = other.mAlpha;
+            mBlurRadius = other.mBlurRadius;
+            mDimmingContainer = other.mDimmingContainer;
+            mRelativeLayer = other.mRelativeLayer;
+        }
+
+        // Same alpha and blur
+        boolean hasSameVisualProperties(Change other) {
+            return Math.abs(mAlpha - other.mAlpha) < EPSILON && mBlurRadius == other.mBlurRadius;
+        }
+
+        boolean hasSameDimmingContainer(Change other) {
+            return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
+        }
+
+        void inheritPropertiesFromAnimation(AnimationSpec anim) {
+            mAlpha = anim.mCurrentAlpha;
+            mBlurRadius = anim.mCurrentBlur;
+        }
+
+        @Override
+        public String toString() {
+            return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+                    + mDimmingContainer + ", relativePosition=" + mRelativeLayer;
+        }
+    }
+
+    private Change mCurrentProperties = new Change();
+    private Change mRequestedProperties = new Change();
+    private AnimationSpec mAlphaAnimationSpec;
+
+    private final AnimationAdapterFactory mAnimationAdapterFactory;
+    private AnimationAdapter mLocalAnimationAdapter;
+
+    DimmerAnimationHelper(AnimationAdapterFactory animationFactory) {
+        mAnimationAdapterFactory = animationFactory;
+    }
+
+    void setExitParameters() {
+        setRequestedRelativeParent(mRequestedProperties.mDimmingContainer, -1 /* relativeLayer */);
+        setRequestedAppearance(0f /* alpha */, 0 /* blur */);
+    }
+
+    // Sets a requested change without applying it immediately
+    void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+        mRequestedProperties.mDimmingContainer = relativeParent;
+        mRequestedProperties.mRelativeLayer = relativeLayer;
+    }
+
+    // Sets a requested change without applying it immediately
+    void setRequestedAppearance(float alpha, int blurRadius) {
+        mRequestedProperties.mAlpha = alpha;
+        mRequestedProperties.mBlurRadius = blurRadius;
+    }
+
+    /**
+     * Commit the last changes we received. Called after
+     * {@link Change#setExitParameters()},
+     * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
+     * {@link Change#setRequestedAppearance(float, int)}
+     */
+    void applyChanges(SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+        if (mRequestedProperties.mDimmingContainer == null) {
+            Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+                    + "call adjustRelativeLayer?");
+            return;
+        }
+        if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+            Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+                    + "does not have a surface");
+            dim.remove(t);
+            return;
+        }
+
+        dim.ensureVisible(t);
+        relativeReparent(dim.mDimSurface,
+                mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+                mRequestedProperties.mRelativeLayer, t);
+
+        if (!mCurrentProperties.hasSameVisualProperties(mRequestedProperties)) {
+            stopCurrentAnimation(dim.mDimSurface);
+
+            if (dim.mSkipAnimation
+                    // If the container doesn't change but requests a dim change, then it is
+                    // directly providing us the animated values
+                    || (mRequestedProperties.hasSameDimmingContainer(mCurrentProperties)
+                    && dim.isDimming())) {
+                ProtoLog.d(WM_DEBUG_DIMMER,
+                        "%s skipping animation and directly setting alpha=%f, blur=%d",
+                        dim, mRequestedProperties.mAlpha,
+                        mRequestedProperties.mBlurRadius);
+                setAlphaBlur(dim.mDimSurface, mRequestedProperties.mAlpha,
+                        mRequestedProperties.mBlurRadius, t);
+                dim.mSkipAnimation = false;
+            } else {
+                startAnimation(t, dim);
+            }
+
+        } else if (!dim.isDimming()) {
+            // We are not dimming, so we tried the exit animation but the alpha is already 0,
+            // therefore, let's just remove this surface
+            dim.remove(t);
+        }
+        mCurrentProperties = new Change(mRequestedProperties);
+    }
+
+    private void startAnimation(
+            SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+        ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
+        mAlphaAnimationSpec = getRequestedAnimationSpec();
+        mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+                dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
+
+        float targetAlpha = mRequestedProperties.mAlpha;
+        int targetBlur = mRequestedProperties.mBlurRadius;
+
+        mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
+                ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
+                    setAlphaBlur(dim.mDimSurface, targetAlpha, targetBlur, t);
+                    if (targetAlpha == 0f && !dim.isDimming()) {
+                        dim.remove(t);
+                    }
+                    mLocalAnimationAdapter = null;
+                    mAlphaAnimationSpec = null;
+                });
+    }
+
+    private boolean isAnimating() {
+        return mAlphaAnimationSpec != null;
+    }
+
+    void stopCurrentAnimation(SurfaceControl surface) {
+        if (mLocalAnimationAdapter != null && isAnimating()) {
+            // Save the current animation progress and cancel the animation
+            mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
+            mLocalAnimationAdapter.onAnimationCancelled(surface);
+            mLocalAnimationAdapter = null;
+            mAlphaAnimationSpec = null;
+        }
+    }
+
+    private AnimationSpec getRequestedAnimationSpec() {
+        final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+        final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+        long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+                * Math.abs(mRequestedProperties.mAlpha - startAlpha));
+
+        final AnimationSpec spec =  new AnimationSpec(
+                new AnimationSpec.AnimationExtremes<>(startAlpha, mRequestedProperties.mAlpha),
+                new AnimationSpec.AnimationExtremes<>(startBlur, mRequestedProperties.mBlurRadius),
+                duration
+        );
+        ProtoLog.v(WM_DEBUG_DIMMER, "Dim animation requested: %s", spec);
+        return spec;
+    }
+
+    /**
+     * Change the relative parent of this dim layer
+     */
+    void relativeReparent(SurfaceControl dimLayer, SurfaceControl relativeParent,
+                          int relativePosition, SurfaceControl.Transaction t) {
+        try {
+            t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
+        } catch (NullPointerException e) {
+            Log.w(TAG, "Tried to change parent of dim " + dimLayer + " after remove", e);
+        }
+    }
+
+    void setAlphaBlur(SurfaceControl sc, float alpha, int blur, SurfaceControl.Transaction t) {
+        try {
+            t.setAlpha(sc, alpha);
+            t.setBackgroundBlurRadius(sc, blur);
+        } catch (NullPointerException e) {
+            Log.w(TAG , "Tried to change look of dim " + sc + " after remove",  e);
+        }
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION_MS * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    /**
+     * Collects the animation specifics
+     */
+    static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationSpec" : TAG_WM;
+
+        static class AnimationExtremes<T> {
+            final T mStartValue;
+            final T mFinishValue;
+
+            AnimationExtremes(T fromValue, T toValue) {
+                mStartValue = fromValue;
+                mFinishValue = toValue;
+            }
+
+            @Override
+            public String toString() {
+                return "[" + mStartValue + "->" + mFinishValue + "]";
+            }
+        }
+
+        private final long mDuration;
+        private final AnimationSpec.AnimationExtremes<Float> mAlpha;
+        private final AnimationSpec.AnimationExtremes<Integer> mBlur;
+
+        float mCurrentAlpha = 0;
+        int mCurrentBlur = 0;
+        boolean mStarted = false;
+
+        AnimationSpec(AnimationSpec.AnimationExtremes<Float> alpha,
+                      AnimationSpec.AnimationExtremes<Integer> blur, long duration) {
+            mAlpha = alpha;
+            mBlur = blur;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            if (!mStarted) {
+                // The first frame would end up in the sync transaction, and since this could be
+                // applied after the animation transaction, we avoid putting visible changes here.
+                // The initial state of the animation matches the current state of the dim anyway.
+                mStarted = true;
+                return;
+            }
+            final float fraction = getFraction(currentPlayTime);
+            mCurrentAlpha =
+                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+            mCurrentBlur =
+                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+            if (sc.isValid()) {
+                t.setAlpha(sc, mCurrentAlpha);
+                t.setBackgroundBlurRadius(sc, mCurrentBlur);
+            } else {
+                Log.w(TAG, "Dimmer#AnimationSpec tried to access " + sc + " after release");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Animation spec: alpha=" + mAlpha + ", blur=" + mBlur;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mAlpha.mStartValue);
+            proto.write(TO, mAlpha.mFinishValue);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+
+    static class AnimationAdapterFactory {
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                                    SurfaceAnimationRunner runner) {
+            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4924810..50376fe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -69,8 +69,6 @@
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
@@ -158,6 +156,8 @@
 import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
+import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
 import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
 import static com.android.window.flags.Flags.explicitRefreshRateHints;
@@ -465,11 +465,20 @@
     boolean mDisplayScalingDisabled;
     final Display mDisplay;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
+
+    /**
+     * Contains the last DisplayInfo override that was sent to DisplayManager or null if we haven't
+     * set an override yet
+     */
+    @Nullable
+    private DisplayInfo mLastDisplayInfoOverride;
+
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final DisplayPolicy mDisplayPolicy;
     private final DisplayRotation mDisplayRotation;
     @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     DisplayFrames mDisplayFrames;
+    private final DisplayUpdater mDisplayUpdater;
 
     private boolean mInTouchMode;
 
@@ -623,7 +632,7 @@
     @VisibleForTesting
     final DeviceStateController mDeviceStateController;
     final Consumer<DeviceStateController.DeviceState> mDeviceStateConsumer;
-    private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
+    final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
     final RemoteDisplayChangeController mRemoteDisplayChangeController;
 
     /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
@@ -1004,6 +1013,24 @@
                 mTmpApplySurfaceChangesTransactionState.obscured;
         final RootWindowContainer root = mWmService.mRoot;
 
+        if (w.mHasSurface) {
+            // Take care of the window being ready to display.
+            final boolean committed = w.mWinAnimator.commitFinishDrawingLocked();
+            if (isDefaultDisplay && committed) {
+                if (w.hasWallpaper()) {
+                    ProtoLog.v(WM_DEBUG_WALLPAPER,
+                            "First draw done in potential wallpaper target %s", w);
+                    mWallpaperMayChange = true;
+                    pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                    if (DEBUG_LAYOUT_REPEATS) {
+                        surfacePlacer.debugLayoutRepeats(
+                                "wallpaper and commitFinishDrawingLocked true",
+                                pendingLayoutChanges);
+                    }
+                }
+            }
+        }
+
         // Update effect.
         w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
 
@@ -1090,30 +1117,9 @@
 
         w.handleWindowMovedIfNeeded();
 
-        final WindowStateAnimator winAnimator = w.mWinAnimator;
-
         //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
         w.resetContentChanged();
 
-        // Moved from updateWindowsAndWallpaperLocked().
-        if (w.mHasSurface) {
-            // Take care of the window being ready to display.
-            final boolean committed = winAnimator.commitFinishDrawingLocked();
-            if (isDefaultDisplay && committed) {
-                if (w.hasWallpaper()) {
-                    ProtoLog.v(WM_DEBUG_WALLPAPER,
-                            "First draw done in potential wallpaper target %s", w);
-                    mWallpaperMayChange = true;
-                    pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                    if (DEBUG_LAYOUT_REPEATS) {
-                        surfacePlacer.debugLayoutRepeats(
-                                "wallpaper and commitFinishDrawingLocked true",
-                                pendingLayoutChanges);
-                    }
-                }
-            }
-        }
-
         final ActivityRecord activity = w.mActivityRecord;
         if (activity != null && activity.isVisibleRequested()) {
             activity.updateLetterboxSurface(w);
@@ -1152,6 +1158,7 @@
         mWallpaperController.resetLargestDisplay(display);
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
+        mDisplayUpdater = new ImmediateDisplayUpdater(this);
         mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
                 * mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -1641,7 +1648,7 @@
                 ? new Transition.ReadyCondition("displayConfig", this) : null;
         if (displayConfig != null) {
             mTransitionController.waitFor(displayConfig);
-        } else if (mTransitionController.isShellTransitionsEnabled()) {
+        } else if (mTransitionController.isShellTransitionsEnabled() && mLastHasContent) {
             Slog.e(TAG, "Display reconfigured outside of a transition: " + this);
         }
         final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
@@ -1920,28 +1927,6 @@
         return true;
     }
 
-    /** Returns {@code true} if the IME is possible to show on the launching activity. */
-    boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
-        final WindowState win = r.findMainWindow(false /* exclude starting window */);
-        if (win == null) {
-            return false;
-        }
-        // See InputMethodManagerService#shouldRestoreImeVisibility that we expecting the IME
-        // should be hidden when the window set the hidden softInputMode.
-        final int softInputMode = win.mAttrs.softInputMode;
-        switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
-            case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
-            case SOFT_INPUT_STATE_HIDDEN:
-                return false;
-        }
-        final boolean useIme = r.getWindow(
-                w -> WindowManager.LayoutParams.mayUseInputMethod(w.mAttrs.flags)) != null;
-        if (!useIme) {
-            return false;
-        }
-        return r.mLastImeShown || (r.mStartingData != null && r.mStartingData.hasImeSurface());
-    }
-
     /** Returns {@code true} if the top activity is transformed with the new rotation of display. */
     boolean hasTopFixedRotationLaunchingApp() {
         return mFixedRotationLaunchingApp != null
@@ -2304,8 +2289,7 @@
 
         computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
 
-        mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
-                mDisplayInfo);
+        setDisplayInfoOverride();
 
         if (isDefaultDisplay) {
             mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
@@ -2317,6 +2301,20 @@
         return mDisplayInfo;
     }
 
+    /**
+     * Sets the current DisplayInfo in DisplayContent as an override to DisplayManager
+     */
+    private void setDisplayInfoOverride() {
+        mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
+                mDisplayInfo);
+
+        if (mLastDisplayInfoOverride == null) {
+            mLastDisplayInfoOverride = new DisplayInfo();
+        }
+
+        mLastDisplayInfoOverride.copyFrom(mDisplayInfo);
+    }
+
     DisplayCutout calculateDisplayCutoutForRotation(int rotation) {
         return mDisplayCutoutCache.getOrCompute(
                 mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation)
@@ -2888,12 +2886,15 @@
         return orientation;
     }
 
-    void updateDisplayInfo() {
+    void updateDisplayInfo(@NonNull DisplayInfo newDisplayInfo) {
         // Check if display metrics changed and update base values if needed.
-        updateBaseDisplayMetricsIfNeeded();
+        updateBaseDisplayMetricsIfNeeded(newDisplayInfo);
 
-        mDisplay.getDisplayInfo(mDisplayInfo);
-        mDisplay.getMetrics(mDisplayMetrics);
+        // Update mDisplayInfo with (newDisplayInfo + mLastDisplayInfoOverride) as
+        // updateBaseDisplayMetricsIfNeeded could have updated mLastDisplayInfoOverride
+        copyDisplayInfoFields(/* out= */ mDisplayInfo, /* base= */ newDisplayInfo,
+                /* override= */ mLastDisplayInfoOverride, /* fields= */ WM_OVERRIDE_FIELDS);
+        mDisplayInfo.getAppMetrics(mDisplayMetrics, mDisplay.getDisplayAdjustments());
 
         onDisplayInfoChanged();
         onDisplayChanged(this);
@@ -2979,9 +2980,9 @@
      * If display metrics changed, overrides are not set and it's not just a rotation - update base
      * values.
      */
-    private void updateBaseDisplayMetricsIfNeeded() {
+    private void updateBaseDisplayMetricsIfNeeded(DisplayInfo newDisplayInfo) {
         // Get real display metrics without overrides from WM.
-        mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mDisplayInfo);
+        mDisplayInfo.copyFrom(newDisplayInfo);
         final int currentRotation = getRotation();
         final int orientation = mDisplayInfo.rotation;
         final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
@@ -3013,7 +3014,7 @@
                 // metrics are updated as rotation settings might depend on them
                 mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this,
                         /* includeRotationSettings */ false);
-                mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId,
+                mDisplayUpdater.onDisplayContentDisplayPropertiesPreChanged(mDisplayId,
                         mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
                 mDisplayRotation.physicalDisplayChanged();
                 mDisplayPolicy.physicalDisplayChanged();
@@ -3049,8 +3050,8 @@
 
             if (physicalDisplayChanged) {
                 mDisplayPolicy.physicalDisplayUpdated();
-                mDisplaySwitchTransitionLauncher.onDisplayUpdated(currentRotation, getRotation(),
-                        getDisplayAreaInfo());
+                mDisplayUpdater.onDisplayContentDisplayPropertiesPostChanged(currentRotation,
+                        getRotation(), getDisplayAreaInfo());
             }
         }
     }
@@ -5497,8 +5498,7 @@
             mDisplayReady = true;
 
             if (mWmService.mDisplayManagerInternal != null) {
-                mWmService.mDisplayManagerInternal
-                        .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
+                setDisplayInfoOverride();
                 configureDisplayPolicy();
             }
 
@@ -5587,12 +5587,7 @@
     void prepareSurfaces() {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
         try {
-            final Transaction transaction = getPendingTransaction();
             super.prepareSurfaces();
-
-            // TODO: Once we totally eliminate global transaction we will pass transaction in here
-            //       rather than merging to global.
-            SurfaceControl.mergeToGlobalTransaction(transaction);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -6146,9 +6141,17 @@
         return mMetricsLogger;
     }
 
-    void onDisplayChanged() {
+    /**
+     * Triggers an update of DisplayInfo from DisplayManager
+     * @param onDisplayChangeApplied callback that is called when the changes are applied
+     */
+    void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
+        mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+    }
+
+    void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
         final int lastDisplayState = mDisplayInfo.state;
-        updateDisplayInfo();
+        updateDisplayInfo(newDisplayInfo);
 
         // The window policy is responsible for stopping activities on the default display.
         final int displayId = mDisplay.getDisplayId();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b34f912..b862d7c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -99,6 +99,7 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
@@ -781,6 +782,12 @@
             if (!mDisplayContent.isDefaultDisplay) {
                 return;
             }
+            if (awake) {
+                mService.mAtmService.mVisibleDozeUiProcess = null;
+            } else if (mScreenOnFully && mNotificationShade != null) {
+                // Screen is still on, so it may be showing an always-on UI.
+                mService.mAtmService.mVisibleDozeUiProcess = mNotificationShade.getProcess();
+            }
             mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                     mAwake /* waiting */);
         }
@@ -826,12 +833,24 @@
     }
 
     public void screenTurnedOn(ScreenOnListener screenOnListener) {
+        WindowProcessController visibleDozeUiProcess = null;
         synchronized (mLock) {
             mScreenOnEarly = true;
             mScreenOnFully = false;
             mKeyguardDrawComplete = false;
             mWindowManagerDrawComplete = false;
             mScreenOnListener = screenOnListener;
+            if (!mAwake && mNotificationShade != null) {
+                // The screen is turned on without awake state. It is usually triggered by an
+                // adding notification, so make the UI process have a higher priority.
+                visibleDozeUiProcess = mNotificationShade.getProcess();
+                mService.mAtmService.mVisibleDozeUiProcess = visibleDozeUiProcess;
+            }
+        }
+        // The method calls AM directly, so invoke it outside the lock.
+        if (visibleDozeUiProcess != null) {
+            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurnedOnWhileDozing");
+            mService.mAtmService.setProcessAnimatingWhileDozing(visibleDozeUiProcess);
         }
     }
 
@@ -842,6 +861,7 @@
             mKeyguardDrawComplete = false;
             mWindowManagerDrawComplete = false;
             mScreenOnListener = null;
+            mService.mAtmService.mVisibleDozeUiProcess = null;
         }
     }
 
@@ -1717,11 +1737,12 @@
     void onOverlayChanged() {
         updateCurrentUserResources();
         // Update the latest display size, cutout.
-        mDisplayContent.updateDisplayInfo();
-        onConfigurationChanged();
-        if (!CLIENT_TRANSIENT) {
-            mSystemGestures.onConfigurationChanged();
-        }
+        mDisplayContent.requestDisplayUpdate(() -> {
+            onConfigurationChanged();
+            if (!CLIENT_TRANSIENT) {
+                mSystemGestures.onConfigurationChanged();
+            }
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a1b8949..d376613 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -281,7 +281,7 @@
         mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
         mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
 
-        int defaultRotation = readDefaultDisplayRotation(displayAddress);
+        int defaultRotation = readDefaultDisplayRotation(displayAddress, displayContent);
         mRotation = defaultRotation;
 
         mDisplayRotationCoordinator = displayRotationCoordinator;
@@ -327,22 +327,31 @@
     }
 
     // Change the default value to the value specified in the sysprop
-    // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+    // ro.bootanim.set_orientation_<physical_display_id> or
+    // ro.bootanim.set_orientation_logical_<logical_display_id>.
+    // Four values are supported: ORIENTATION_0,
     // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
     // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
     // This is needed to support having default orientation different from the natural
     // device orientation. For example, on tablets that may want to keep natural orientation
     // portrait for applications compatibility but have landscape orientation as a default choice
     // from the UX perspective.
+    // On watches that may want to keep the wrist orientation as the default.
     @Surface.Rotation
-    private int readDefaultDisplayRotation(DisplayAddress displayAddress) {
-        if (!(displayAddress instanceof DisplayAddress.Physical)) {
-            return Surface.ROTATION_0;
+    private int readDefaultDisplayRotation(DisplayAddress displayAddress,
+            DisplayContent displayContent) {
+        String syspropValue = "";
+        if (displayAddress instanceof DisplayAddress.Physical) {
+            final DisplayAddress.Physical physicalAddress =
+                    (DisplayAddress.Physical) displayAddress;
+            syspropValue = SystemProperties.get(
+                    "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(), "");
         }
-        final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) displayAddress;
-        String syspropValue = SystemProperties.get(
-                "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(),
-                "ORIENTATION_0");
+        if ("".equals(syspropValue) && displayContent.isDefaultDisplay) {
+            syspropValue = SystemProperties.get(
+                    "ro.bootanim.set_orientation_logical_" + displayContent.getDisplayId(), "");
+        }
+
         if (syspropValue.equals("ORIENTATION_90")) {
             return Surface.ROTATION_90;
         } else if (syspropValue.equals("ORIENTATION_180")) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 534cdc2..e808dec 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -38,7 +38,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.RefreshCallbackItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -226,13 +225,12 @@
             ProtoLog.v(WM_DEBUG_STATES,
                     "Refreshing activity for camera compatibility treatment, "
                             + "activityRecord=%s", activity);
-            final ClientTransaction transaction = ClientTransaction.obtain(
-                    activity.app.getThread());
-            transaction.addCallback(RefreshCallbackItem.obtain(activity.token,
-                            cycleThroughStop ? ON_STOP : ON_PAUSE));
-            transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(activity.token,
-                    /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
-            activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+            final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+                    activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+            final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                    activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
             mHandler.postDelayed(
                     () -> onActivityRefreshed(activity),
                     REFRESH_CALLBACK_TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java
new file mode 100644
index 0000000..e611177
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayUpdater.java
@@ -0,0 +1,52 @@
+/*
+ * 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.wm;
+
+import android.annotation.NonNull;
+import android.view.Surface;
+import android.window.DisplayAreaInfo;
+
+/**
+ * Interface for a helper class that manages updates of DisplayInfo coming from DisplayManager
+ */
+interface DisplayUpdater {
+    /**
+     * Reads the latest display parameters from the display manager and returns them in a callback.
+     * If there are pending display updates, it will wait for them to finish first and only then it
+     * will call the callback with the latest display parameters.
+     *
+     * @param callback is called when all pending display updates are finished
+     */
+    void updateDisplayInfo(@NonNull Runnable callback);
+
+    /**
+     * Called when physical display has changed and before DisplayContent has applied new display
+     * properties
+     */
+    default void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth,
+            int initialDisplayHeight, int newWidth, int newHeight) {
+    }
+
+    /**
+     * Called after physical display has changed and after DisplayContent applied new display
+     * properties
+     */
+    default void onDisplayContentDisplayPropertiesPostChanged(
+            @Surface.Rotation int previousRotation, @Surface.Rotation int newRotation,
+            @NonNull DisplayAreaInfo newDisplayAreaInfo) {
+    }
+}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 1670b36e..b7eab08 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -30,7 +30,6 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 
@@ -100,10 +99,10 @@
         }
     }
 
-    void remove(IWindow client) {
+    void remove(IBinder client) {
         for (int i = mWindows.size() - 1; i >= 0; i--) {
             EmbeddedWindow ew = mWindows.valueAt(i);
-            if (ew.mClient == client.asBinder()) {
+            if (ew.mClient == client) {
                 mWindows.removeAt(i).onRemoved();
                 mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
                 mWindowsByWindowToken.remove(ew.getWindowToken());
diff --git a/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java
new file mode 100644
index 0000000..4af9013
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java
@@ -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.server.wm;
+
+import android.annotation.NonNull;
+import android.view.DisplayInfo;
+import android.window.DisplayAreaInfo;
+
+/**
+ * DisplayUpdater that immediately applies new DisplayInfo properties
+ */
+public class ImmediateDisplayUpdater implements DisplayUpdater {
+
+    private final DisplayContent mDisplayContent;
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+
+    public ImmediateDisplayUpdater(@NonNull DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+        mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+    }
+
+    @Override
+    public void updateDisplayInfo(Runnable callback) {
+        mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
+                mDisplayContent.mDisplayId, mDisplayInfo);
+        mDisplayContent.onDisplayInfoUpdated(mDisplayInfo);
+        callback.run();
+    }
+
+    @Override
+    public void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth,
+            int initialDisplayHeight, int newWidth, int newHeight) {
+        mDisplayContent.mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(
+                displayId, initialDisplayWidth, initialDisplayHeight, newWidth, newHeight);
+    }
+
+    @Override
+    public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
+            DisplayAreaInfo newDisplayAreaInfo) {
+        mDisplayContent.mDisplaySwitchTransitionLauncher.onDisplayUpdated(previousRotation,
+                newRotation,
+                newDisplayAreaInfo);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 997b608..14912d0 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -439,8 +439,10 @@
                         final InputMethodManagerInternal inputMethodManagerInternal =
                                 LocalServices.getService(InputMethodManagerInternal.class);
                         if (inputMethodManagerInternal != null) {
-                            inputMethodManagerInternal.hideCurrentInputMethod(
-                                    SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
+                            // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
+                            inputMethodManagerInternal.hideAllInputMethods(
+                                    SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
+                                    mDisplayContent.getDisplayId());
                         }
                         // Ensure removing the IME snapshot when the app no longer to show on the
                         // task snapshot (also taking the new task snaphot to update the overview).
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0d7f49..e6bbd52 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -101,10 +101,8 @@
         mPolicy = displayContent.getDisplayPolicy();
         final Resources r = mPolicy.getContext().getResources();
         mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
-        mTransientControlTarget = new ControlTarget(
-                stateController, displayContent.mWmService.mH, "TransientControlTarget");
-        mPermanentControlTarget = new ControlTarget(
-                stateController, displayContent.mWmService.mH, "PermanentControlTarget");
+        mTransientControlTarget = new ControlTarget(displayContent, "TransientControlTarget");
+        mPermanentControlTarget = new ControlTarget(displayContent, "PermanentControlTarget");
     }
 
     /** Updates the target which can control system bars. */
@@ -699,24 +697,35 @@
         }
     }
 
-    private static class ControlTarget implements InsetsControlTarget {
+    private static class ControlTarget implements InsetsControlTarget, Runnable {
 
+        private final Handler mHandler;
+        private final Object mGlobalLock;
         private final InsetsState mState = new InsetsState();
-        private final InsetsController mInsetsController;
         private final InsetsStateController mStateController;
+        private final InsetsController mInsetsController;
         private final String mName;
 
-        ControlTarget(InsetsStateController stateController, Handler handler, String name) {
-            mStateController = stateController;
-            mInsetsController = new InsetsController(new Host(handler, name));
+        ControlTarget(DisplayContent displayContent, String name) {
+            mHandler = displayContent.mWmService.mH;
+            mGlobalLock = displayContent.mWmService.mGlobalLock;
+            mStateController = displayContent.getInsetsStateController();
+            mInsetsController = new InsetsController(new Host(mHandler, name));
             mName = name;
         }
 
         @Override
         public void notifyInsetsControlChanged() {
-            mState.set(mStateController.getRawInsetsState(), true /* copySources */);
-            mInsetsController.onStateChanged(mState);
-            mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            synchronized (mGlobalLock) {
+                mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+                mInsetsController.onStateChanged(mState);
+                mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b738c1c..5269d35 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -307,7 +307,7 @@
                 mService.stopAppSwitches();
             }
 
-            mWindowManager.inSurfaceTransaction(() -> {
+            inSurfaceTransaction(() -> {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
                 mService.deferWindowLayout();
@@ -419,6 +419,11 @@
         }
     }
 
+    // No-op wrapper to keep legacy code.
+    private static void inSurfaceTransaction(Runnable exec) {
+        exec.run();
+    }
+
     /** Gives the owner of recents animation higher priority. */
     private void setProcessAnimating(boolean animating) {
         if (mCaller == null) return;
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index eb639b6..a98b9f7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -307,7 +307,6 @@
             mIsFinishing = true;
             unlinkToDeathOfRunner();
             releaseFinishedCallback();
-            mService.openSurfaceTransaction();
             try {
                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
                         "onAnimationFinished(): Notify animation finished:");
@@ -348,7 +347,6 @@
                 Slog.e(TAG, "Failed to finish remote animation", e);
                 throw e;
             } finally {
-                mService.closeSurfaceTransaction("RemoteAnimationController#finished");
                 mIsFinishing = false;
             }
             // Reset input for all activities when the remote animation is finished.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fe2c250..522e7d2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -75,7 +75,6 @@
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
@@ -628,7 +627,7 @@
     void refreshSecureSurfaceState() {
         forAllWindows((w) -> {
             if (w.mHasSurface) {
-                w.mWinAnimator.setSecureLocked(w.isSecureLocked());
+                w.setSecureLocked(w.isSecureLocked());
             }
         }, true /* traverseTopToBottom */);
     }
@@ -788,23 +787,13 @@
         final DisplayContent defaultDisplay = mWmService.getDefaultDisplayContentLocked();
         final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
 
-        if (SHOW_LIGHT_TRANSACTIONS) {
-            Slog.i(TAG,
-                    ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
-        }
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
-        mWmService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction();
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
-            mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            if (SHOW_LIGHT_TRANSACTIONS) {
-                Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
-            }
         }
 
         // Send any pending task-info changes that were queued-up during a layout deferment
@@ -998,9 +987,6 @@
         // Give the display manager a chance to adjust properties like display rotation if it needs
         // to.
         mWmService.mDisplayManagerInternal.performTraversal(t);
-        if (t != defaultDc.mSyncTransaction) {
-            SurfaceControl.mergeToGlobalTransaction(t);
-        }
     }
 
     /**
@@ -2185,12 +2171,16 @@
                     // now, it will take focus briefly which confuses the RecentTasks tracker.
                     rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
                 }
-
+                // Temporarily disable focus when reparenting to avoid intermediate focus change
+                // (because the task is on top and the activity is resumed), which could cause the
+                // task to be added in recents task list unexpectedly.
+                rootTask.setFocusable(false);
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
                 // On the other hand, ActivityRecord#onParentChanged takes care of setting the
                 // up-to-dated root pinned task information on this newly created root task.
                 r.reparent(rootTask, MAX_VALUE, reason);
+                rootTask.setFocusable(true);
 
                 // Ensure the leash of new task is in sync with its current bounds after reparent.
                 rootTask.maybeApplyLastRecentsAnimationTransaction();
@@ -2730,15 +2720,20 @@
         synchronized (mService.mGlobalLock) {
             final DisplayContent displayContent = getDisplayContent(displayId);
             if (displayContent != null) {
-                displayContent.onDisplayChanged();
+                displayContent.requestDisplayUpdate(() -> clearDisplayInfoCaches(displayId));
+            } else {
+                clearDisplayInfoCaches(displayId);
             }
-            // Drop any cached DisplayInfos associated with this display id - the values are now
-            // out of date given this display changed event.
-            mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
-            updateDisplayImePolicyCache();
         }
     }
 
+    private void clearDisplayInfoCaches(int displayId) {
+        // Drop any cached DisplayInfos associated with this display id - the values are now
+        // out of date given this display changed event.
+        mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
+        updateDisplayImePolicyCache();
+    }
+
     void updateDisplayImePolicyCache() {
         ArrayMap<Integer, Integer> displayImePolicyMap = new ArrayMap<>();
         forAllDisplays(dc -> displayImePolicyMap.put(dc.getDisplayId(), dc.getImePolicy()));
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index bbb8563..e7bffdf 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -32,6 +32,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
+import static com.android.window.flags.Flags.deleteCaptureDisplay;
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
@@ -170,7 +171,7 @@
 
         try {
             final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
-            if (isSizeChanged) {
+            if (isSizeChanged && !deleteCaptureDisplay()) {
                 final DisplayAddress address = displayInfo.address;
                 if (!(address instanceof DisplayAddress.Physical)) {
                     Slog.e(TAG, "Display does not have a physical address: " + displayId);
@@ -196,6 +197,19 @@
                                 .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
+            } else if (isSizeChanged) {
+                // Temporarily not skip screenshot for the rounded corner overlays and screenshot
+                // the whole display to include the rounded corner overlays.
+                setSkipScreenshotForRoundedCornerOverlays(false, t);
+                ScreenCapture.LayerCaptureArgs captureArgs =
+                        new ScreenCapture.LayerCaptureArgs.Builder(
+                                displayContent.getSurfaceControl())
+                                .setCaptureSecureLayers(true)
+                                .setAllowProtected(true)
+                                .setSourceCrop(new Rect(0, 0, width, height))
+                                .setHintForSeamlessTransition(true)
+                                .build();
+                screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
             } else {
                 ScreenCapture.LayerCaptureArgs captureArgs =
                         new ScreenCapture.LayerCaptureArgs.Builder(
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 18d64d7..56f9aa4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -253,8 +253,8 @@
     }
 
     @Override
-    public void remove(IWindow window) {
-        mService.removeWindow(this, window);
+    public void remove(IBinder clientToken) {
+        mService.removeClientToken(this, clientToken);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
index 2549bbf..b5d94a2 100644
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -17,265 +17,203 @@
 package com.android.server.wm;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.graphics.Rect;
 import android.util.Log;
-import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 
-import java.io.PrintWriter;
-
 class SmoothDimmer extends Dimmer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
-    private static final float EPSILON = 0.0001f;
-    // This is in milliseconds.
-    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-    DimState mDimState;
-    private WindowContainer mLastRequestedDimContainer;
-    private final AnimationAdapterFactory mAnimationAdapterFactory;
 
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    DimState mDimState;
+    final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
+
+    /**
+     * Controls the dim behaviour
+     */
     @VisibleForTesting
     class DimState {
-        /**
-         * The layer where property changes should be invoked on.
-         */
-        SurfaceControl mDimLayer;
-        boolean mDimming;
-        boolean mIsVisible;
-
+        /** Related objects */
+        SurfaceControl mDimSurface;
+        final WindowContainer mHostContainer;
+        // The last container to request to dim
+        private WindowContainer mLastRequestedDimContainer;
+        /** Animation */
+        private final DimmerAnimationHelper mAnimationHelper;
+        boolean mSkipAnimation = false;
+        // Determines whether the dim layer should animate before destroying.
+        boolean mAnimateExit = true;
+        /** Surface visibility and bounds */
+        private boolean mIsVisible = false;
         // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
         final Rect mDimBounds = new Rect();
 
-        /**
-         * Determines whether the dim layer should animate before destroying.
-         */
-        boolean mAnimateExit = true;
-
-        /**
-         * Used for Dims not associated with a WindowContainer.
-         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
-         * lifecycle.
-         */
-        boolean mDontReset;
-
-        Change mCurrentProperties;
-        Change mRequestedProperties;
-        private AnimationSpec mAlphaAnimationSpec;
-        private AnimationAdapter mLocalAnimationAdapter;
-
-        static class Change {
-            private float mAlpha = -1f;
-            private int mBlurRadius = -1;
-            private WindowContainer mDimmingContainer = null;
-            private int mRelativeLayer = -1;
-            private boolean mSkipAnimation = false;
-
-            Change() {}
-
-            Change(Change other) {
-                mAlpha = other.mAlpha;
-                mBlurRadius = other.mBlurRadius;
-                mDimmingContainer = other.mDimmingContainer;
-                mRelativeLayer = other.mRelativeLayer;
-            }
-
-            @Override
-            public String toString() {
-                return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
-                        + mDimmingContainer + ", relativePosition=" + mRelativeLayer
-                        + ", skipAnimation=" + mSkipAnimation;
-            }
-        }
-
-        DimState(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-            mDimming = true;
-            mCurrentProperties = new Change();
-            mRequestedProperties = new Change();
-        }
-
-        void setExitParameters(WindowContainer container) {
-            setRequestedRelativeParent(container, -1 /* relativeLayer */);
-            setRequestedAppearance(0f /* alpha */, 0 /* blur */);
-        }
-
-        // Sets a requested change without applying it immediately
-        void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
-            mRequestedProperties.mDimmingContainer = relativeParent;
-            mRequestedProperties.mRelativeLayer = relativeLayer;
-        }
-
-        // Sets a requested change without applying it immediately
-        void setRequestedAppearance(float alpha, int blurRadius) {
-            mRequestedProperties.mAlpha = alpha;
-            mRequestedProperties.mBlurRadius = blurRadius;
-        }
-
-        /**
-         * Commit the last changes we received. Called after
-         * {@link Change#setExitParameters(WindowContainer)},
-         * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
-         * {@link Change#setRequestedAppearance(float, int)}
-         */
-        void applyChanges(SurfaceControl.Transaction t) {
-            if (mRequestedProperties.mDimmingContainer == null) {
-                Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
-                        + "call adjustRelativeLayer?");
-                return;
-            }
-            if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
-                Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
-                        + "does not have a surface");
-                return;
-            }
-            if (!mDimState.mIsVisible) {
-                mDimState.mIsVisible = true;
-                t.show(mDimState.mDimLayer);
-            }
-            t.setRelativeLayer(mDimLayer,
-                    mRequestedProperties.mDimmingContainer.getSurfaceControl(),
-                    mRequestedProperties.mRelativeLayer);
-
-            if (aspectChanged()) {
-                if (isAnimating()) {
-                    mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
-                }
-                if (mRequestedProperties.mSkipAnimation
-                        || (!dimmingContainerChanged() && mDimming)) {
-                    // If the dimming container has not changed, then it is running its own
-                    // animation, thus we can directly set the values we get requested, unless it's
-                    // the exiting animation
-                    ProtoLog.d(WM_DEBUG_DIMMER,
-                            "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
-                            mDimLayer, mRequestedProperties.mAlpha,
-                            mRequestedProperties.mBlurRadius);
-                    t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
-                    t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
-                    mRequestedProperties.mSkipAnimation = false;
-                } else {
-                    startAnimation(t);
-                }
-            }
-            mCurrentProperties = new Change(mRequestedProperties);
-        }
-
-        private void startAnimation(SurfaceControl.Transaction t) {
-            mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
-                    mRequestedProperties.mBlurRadius);
-            mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
-                    mHost.mWmService.mSurfaceAnimationRunner);
-
-            mLocalAnimationAdapter.startAnimation(mDimLayer, t,
-                    ANIMATION_TYPE_DIMMER, (type, animator) -> {
-                        t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
-                        t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
-                        if (mRequestedProperties.mAlpha == 0f && !mDimming) {
-                            ProtoLog.d(WM_DEBUG_DIMMER,
-                                    "Removing dim surface %s on transaction %s", mDimLayer, t);
-                            t.remove(mDimLayer);
-                        }
-                        mLocalAnimationAdapter = null;
-                        mAlphaAnimationSpec = null;
-                    });
-        }
-
-        private boolean isAnimating() {
-            return mAlphaAnimationSpec != null;
-        }
-
-        private boolean aspectChanged() {
-            return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
-                    || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
-        }
-
-        private boolean dimmingContainerChanged() {
-            return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
-        }
-
-        private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
-            final float startAlpha;
-            final int startBlur;
-            if (mAlphaAnimationSpec != null) {
-                startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
-                startBlur = mAlphaAnimationSpec.mCurrentBlur;
-            } else {
-                startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
-                startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
-            }
-            long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
-                    * Math.abs(targetAlpha - startAlpha));
-
-            ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
-                            + "alpha: %f -> %f, blur: %d -> %d",
-                    mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
-                    startBlur, targetBlur);
-            return new AnimationSpec(
-                    new AnimationExtremes<>(startAlpha, targetAlpha),
-                    new AnimationExtremes<>(startBlur, targetBlur),
-                    duration
-            );
-        }
-    }
-
-    protected SmoothDimmer(WindowContainer host) {
-        this(host, new AnimationAdapterFactory());
-    }
-
-    @VisibleForTesting
-    SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
-        super(host);
-        mAnimationAdapterFactory = animationFactory;
-    }
-
-    private DimState obtainDimState(WindowContainer container) {
-        if (mDimState == null) {
+        DimState() {
+            mHostContainer = mHost;
+            mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
             try {
-                final SurfaceControl ctl = makeDimLayer();
-                mDimState = new DimState(ctl);
+                mDimSurface = makeDimLayer();
             } catch (Surface.OutOfResourcesException e) {
                 Log.w(TAG, "OutOfResourcesException creating dim surface");
             }
         }
 
-        mLastRequestedDimContainer = container;
-        return mDimState;
+        void ensureVisible(SurfaceControl.Transaction t) {
+            if (!mIsVisible) {
+                t.show(mDimSurface);
+                t.setAlpha(mDimSurface, 0f);
+                mIsVisible = true;
+            }
+        }
+
+        void adjustSurfaceLayout(SurfaceControl.Transaction t) {
+            // TODO: Once we use geometry from hierarchy this falls away.
+            t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
+            t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
+        }
+
+        /**
+         * Set the parameters to prepare the dim to change its appearance
+         */
+        void prepareLookChange(float alpha, int blurRadius) {
+            mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
+        }
+
+        /**
+         * Prepare the dim for the exit animation
+         */
+        void exit(SurfaceControl.Transaction t) {
+            if (!mAnimateExit) {
+                remove(t);
+            } else {
+                mAnimationHelper.setExitParameters();
+                setReady(t);
+            }
+        }
+
+        void remove(SurfaceControl.Transaction t) {
+            mAnimationHelper.stopCurrentAnimation(mDimSurface);
+            if (mDimSurface.isValid()) {
+                t.remove(mDimSurface);
+                ProtoLog.d(WM_DEBUG_DIMMER,
+                        "Removing dim surface %s on transaction %s", this, t);
+            } else {
+                Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
+        }
+
+        /**
+         * Set the parameters to prepare the dim to be relative parented to the dimming container
+         */
+        void prepareReparent(WindowContainer relativeParent, int relativeLayer) {
+            mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+        }
+
+        /**
+         * Call when all the changes have been requested to have them applied
+         * @param t The transaction in which to apply the changes
+         */
+        void setReady(SurfaceControl.Transaction t) {
+            mAnimationHelper.applyChanges(t, this);
+        }
+
+        /**
+         * Whether anyone is currently requesting the dim
+         */
+        boolean isDimming() {
+            return mLastRequestedDimContainer != null;
+        }
+
+        private SurfaceControl makeDimLayer() {
+            return mHost.makeChildSurface(null)
+                    .setParent(mHost.getSurfaceControl())
+                    .setColorLayer()
+                    .setName("Dim Layer for - " + mHost.getName())
+                    .setCallsite("DimLayer.makeDimLayer")
+                    .build();
+        }
     }
 
-    private SurfaceControl makeDimLayer() {
-        return mHost.makeChildSurface(null)
-                .setParent(mHost.getSurfaceControl())
-                .setColorLayer()
-                .setName("Dim Layer for - " + mHost.getName())
-                .setCallsite("Dimmer.makeDimLayer")
-                .build();
+    protected SmoothDimmer(WindowContainer host) {
+        this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
     }
 
-    @Override
-    SurfaceControl getDimLayer() {
-        return mDimState != null ? mDimState.mDimLayer : null;
+    @VisibleForTesting
+    SmoothDimmer(WindowContainer host,
+                 DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
+        super(host);
+        mAnimationAdapterFactory = animationFactory;
     }
 
     @Override
     void resetDimStates() {
+        if (mDimState != null) {
+            mDimState.mLastRequestedDimContainer = null;
+        }
+    }
+
+    @Override
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+        d.prepareLookChange(alpha, blurRadius);
+    }
+
+    @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        if (mDimState != null) {
+            mDimState.prepareReparent(container, relativeLayer);
+        }
+    }
+
+    @Override
+    boolean updateDims(SurfaceControl.Transaction t) {
         if (mDimState == null) {
-            return;
+            return false;
         }
-        if (!mDimState.mDontReset) {
-            mDimState.mDimming = false;
+        if (!mDimState.isDimming()) {
+            // No one is dimming, fade out and remove the dim
+            mDimState.exit(t);
+            mDimState = null;
+            return false;
+        } else {
+            // Someone is dimming, show the requested changes
+            mDimState.adjustSurfaceLayout(t);
+            final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
+            if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+                    && ws.mActivityRecord.mStartingData != null) {
+                // Skip enter animation while starting window is on top of its activity
+                mDimState.mSkipAnimation = true;
+            }
+            mDimState.setReady(t);
+            return true;
         }
     }
 
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            mDimState = new DimState();
+        }
+        mDimState.mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    @Override
+    @VisibleForTesting
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimSurface : null;
+    }
+
     @Override
     Rect getDimBounds() {
         return mDimState != null ? mDimState.mDimBounds : null;
@@ -287,127 +225,4 @@
             mDimState.mAnimateExit = false;
         }
     }
-
-    @Override
-    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
-        final DimState d = obtainDimState(container);
-        mDimState.setRequestedAppearance(alpha, blurRadius);
-        d.mDimming = true;
-    }
-
-    @Override
-    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
-        if (mDimState != null) {
-            mDimState.setRequestedRelativeParent(container, relativeLayer);
-        }
-    }
-
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-
-        if (!mDimState.mDimming) {
-            // No one is dimming anymore, fade out dim and remove
-            if (!mDimState.mAnimateExit) {
-                if (mDimState.mDimLayer.isValid()) {
-                    t.remove(mDimState.mDimLayer);
-                }
-            } else {
-                mDimState.setExitParameters(
-                        mDimState.mRequestedProperties.mDimmingContainer);
-                mDimState.applyChanges(t);
-            }
-            mDimState = null;
-            return false;
-        }
-        final Rect bounds = mDimState.mDimBounds;
-        // TODO: Once we use geometry from hierarchy this falls away.
-        t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
-        t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
-        // Skip enter animation while starting window is on top of its activity
-        final WindowState ws = mLastRequestedDimContainer.asWindowState();
-        if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
-                && ws.mActivityRecord.mStartingData != null) {
-            mDimState.mRequestedProperties.mSkipAnimation = true;
-        }
-        mDimState.applyChanges(t);
-        return true;
-    }
-
-    private long getDimDuration(WindowContainer container) {
-        // Use the same duration as the animation on the WindowContainer
-        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
-        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
-        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
-                : animationAdapter.getDurationHint();
-    }
-
-    private static class AnimationExtremes<T> {
-        final T mStartValue;
-        final T mFinishValue;
-
-        AnimationExtremes(T fromValue, T toValue) {
-            mStartValue = fromValue;
-            mFinishValue = toValue;
-        }
-    }
-
-    private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
-        private final long mDuration;
-        private final AnimationExtremes<Float> mAlpha;
-        private final AnimationExtremes<Integer> mBlur;
-
-        float mCurrentAlpha = 0;
-        int mCurrentBlur = 0;
-
-        AnimationSpec(AnimationExtremes<Float> alpha,
-                AnimationExtremes<Integer> blur, long duration) {
-            mAlpha = alpha;
-            mBlur = blur;
-            mDuration = duration;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
-            final float fraction = getFraction(currentPlayTime);
-            mCurrentAlpha =
-                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
-            mCurrentBlur =
-                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
-            t.setAlpha(sc, mCurrentAlpha);
-            t.setBackgroundBlurRadius(sc, mCurrentBlur);
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
-            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
-            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
-            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
-            pw.print(" duration="); pw.println(mDuration);
-        }
-
-        @Override
-        public void dumpDebugInner(ProtoOutputStream proto) {
-            final long token = proto.start(ALPHA);
-            proto.write(FROM, mAlpha.mStartValue);
-            proto.write(TO, mAlpha.mFinishValue);
-            proto.write(DURATION_MS, mDuration);
-            proto.end(token);
-        }
-    }
-
-    static class AnimationAdapterFactory {
-
-        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
-                SurfaceAnimationRunner runner) {
-            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 28a35b9..3032110 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -276,12 +276,14 @@
         /**
          * Removes the starting window surface. Do not hold the window manager lock when calling
          * this method!
+         *
          * @param animate Whether need to play the default exit animation for starting window.
+         * @param hasImeSurface Whether the starting window has IME surface.
          */
-        public void remove(boolean animate) {
+        public void remove(boolean animate, boolean hasImeSurface) {
             synchronized (mService.mGlobalLock) {
                 mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
-                        mTaskOrganizer, animate);
+                        mTaskOrganizer, animate, hasImeSurface);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c9703db..de802b9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3513,7 +3513,8 @@
         }
         appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null
                 && !appCompatTaskInfo.topActivityInSizeCompat
-                && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings();
+                && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings()
+                && !info.isTopActivityTransparent;
         appCompatTaskInfo.topActivityBoundsLetterboxed = top != null  && top.areBoundsLetterboxed();
     }
 
@@ -4544,12 +4545,6 @@
      * @param creating {@code true} if this is being run during task construction.
      */
     void setWindowingMode(int preferredWindowingMode, boolean creating) {
-        mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction(
-                preferredWindowingMode, creating));
-    }
-
-    private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode,
-            boolean creating) {
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
         if (taskDisplayArea == null) {
             Slog.d(TAG, "taskDisplayArea is null, bail early");
@@ -4624,7 +4619,12 @@
             if (topActivity != null) {
                 mTaskSupervisor.mNoAnimActivities.add(topActivity);
             }
-            super.setWindowingMode(windowingMode);
+
+            final boolean isPip2ExperimentEnabled =
+                    ActivityTaskManagerService.isPip2ExperimentEnabled();
+            if (!isPip2ExperimentEnabled) {
+                super.setWindowingMode(windowingMode);
+            }
 
             if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
                 // Try reparent pinned activity back to its original task after
@@ -4633,32 +4633,37 @@
                 // PiP, we set final windowing mode on the ActivityRecord first and then on its
                 // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
                 // always performed on its original task, reparent immediately in ActivityRecord
-                // breaks it.
-                if (topActivity.getLastParentBeforePip() != null) {
-                    // Do not reparent if the pinned task is in removal, indicated by the
-                    // force hidden flag.
-                    if (!isForceHidden()) {
-                        final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
-                        if (lastParentBeforePip.isAttached()) {
-                            topActivity.reparent(lastParentBeforePip,
-                                    lastParentBeforePip.getChildCount() /* top */,
-                                    "movePinnedActivityToOriginalTask");
-                            final DisplayContent dc = topActivity.getDisplayContent();
-                            if (dc != null && dc.isFixedRotationLaunchingApp(topActivity)) {
-                                // Expanding pip into new rotation, so create a rotation leash
-                                // until the display is rotated.
-                                topActivity.getOrCreateFixedRotationLeash(
-                                        topActivity.getPendingTransaction());
-                            }
-                            lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
-                        }
+                // breaks it. Do not reparent if the pinned task is in removal, indicated by the
+                // force hidden flag.
+                if (topActivity.getLastParentBeforePip() != null && !isForceHidden()
+                        && topActivity.getLastParentBeforePip().isAttached()) {
+                    // We need to collect the pip activity to allow for screenshots
+                    // to be taken as a part of reparenting.
+                    mTransitionController.collect(topActivity);
+
+                    final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
+                    topActivity.reparent(lastParentBeforePip,
+                            lastParentBeforePip.getChildCount() /* top */,
+                            "movePinnedActivityToOriginalTask");
+                    final DisplayContent dc = topActivity.getDisplayContent();
+                    if (dc != null && dc.isFixedRotationLaunchingApp(topActivity)) {
+                        // Expanding pip into new rotation, so create a rotation leash
+                        // until the display is rotated.
+                        topActivity.getOrCreateFixedRotationLeash(
+                                topActivity.getSyncTransaction());
                     }
+                    lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+                }
+                if (isPip2ExperimentEnabled) {
+                    super.setWindowingMode(windowingMode);
                 }
                 // Resume app-switches-allowed flag when exiting from pinned mode since
                 // it does not follow the ActivityStarter path.
                 if (topActivity.shouldBeVisible()) {
                     mAtmService.resumeAppSwitches();
                 }
+            } else if (isPip2ExperimentEnabled) {
+                super.setWindowingMode(windowingMode);
             }
 
             if (creating) {
@@ -5965,18 +5970,16 @@
                     "Can't exit pinned mode if it's not pinned already.");
         }
 
-        mWmService.inSurfaceTransaction(() -> {
-            final Task task = getBottomMostTask();
-            setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        final Task task = getBottomMostTask();
+        setWindowingMode(WINDOWING_MODE_UNDEFINED);
 
-            // Task could have been removed from the hierarchy due to windowing mode change
-            // where its only child is reparented back to their original parent task.
-            if (isAttached()) {
-                getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
-            }
+        // Task could have been removed from the hierarchy due to windowing mode change
+        // where its only child is reparented back to their original parent task.
+        if (isAttached()) {
+            getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+        }
 
-            mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
-        });
+        mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
     }
 
     private int setBounds(Rect existing, Rect bounds) {
@@ -6174,6 +6177,7 @@
                 // Avoid resuming activities on secondary displays since we don't want bubble
                 // activities to be resumed while bubble is still collapsed.
                 // TODO(b/113840485): Having keyguard going away state for secondary displays.
+                && display != null
                 && display.isDefaultDisplay) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 197edc3..8bc461f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1793,7 +1793,7 @@
             EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
                     prev.shortComponentName, "userLeaving=" + userLeaving, reason);
 
-            mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
+            mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
                     PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
                             prev.configChangeFlags, pauseImmediately, autoEnteringPip));
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index f148176..3a711b2 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -394,7 +394,7 @@
             boolean taskAppearedSent = t.mTaskAppearedSent;
             if (taskAppearedSent) {
                 if (t.getSurfaceControl() != null) {
-                    t.migrateToNewSurfaceControl(t.getPendingTransaction());
+                    t.migrateToNewSurfaceControl(t.getSyncTransaction());
                 }
                 t.mTaskAppearedSent = false;
             }
@@ -673,7 +673,8 @@
         return true;
     }
 
-    void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation) {
+    void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation,
+            boolean hasImeSurface) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
             return;
@@ -693,13 +694,13 @@
         if (topActivity != null) {
             // Set defer remove mode for IME
             final DisplayContent dc = topActivity.getDisplayContent();
-            final WindowState imeWindow = dc.mInputMethodWindow;
-            if (topActivity.isVisibleRequested() && imeWindow != null
-                    && dc.mayImeShowOnLaunchingActivity(topActivity)
-                    && dc.isFixedRotationLaunchingApp(topActivity)) {
-                removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
-            } else if (dc.mayImeShowOnLaunchingActivity(topActivity)) {
-                removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+            if (hasImeSurface) {
+                if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null
+                        && dc.isFixedRotationLaunchingApp(topActivity)) {
+                    removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+                } else {
+                    removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+                }
             } else {
                 removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
             }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index caa57bb..642d22f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1737,8 +1737,8 @@
         // Since we created root-leash but no longer reference it from core, release it now
         info.releaseAnimSurfaces();
 
-        mLogger.logOnSendAsync(mController.mLoggerHandler);
         if (mLogger.mInfo != null) {
+            mLogger.logOnSendAsync(mController.mLoggerHandler);
             mController.mTransitionTracer.logSentTransition(this, mTargets);
         }
     }
@@ -3305,7 +3305,6 @@
          */
         void addGroup(WindowContainer wc) {
             if (mReadyGroups.containsKey(wc)) {
-                Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
                 return;
             }
             mReadyGroups.put(wc, false);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a736874..bacfda5 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -992,11 +992,19 @@
     private void enforceSurfaceVisible(WindowContainer<?> wc) {
         if (wc.mSurfaceControl == null) return;
         wc.getSyncTransaction().show(wc.mSurfaceControl);
+        final ActivityRecord ar = wc.asActivityRecord();
+        if (ar != null) {
+            ar.mLastSurfaceShowing = true;
+        }
         // Force showing the parents because they may be hidden by previous transition.
         for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
                 p = p.getParent()) {
             if (p.mSurfaceControl != null) {
                 p.getSyncTransaction().show(p.mSurfaceControl);
+                final Task task = p.asTask();
+                if (task != null) {
+                    task.mLastSurfaceShowing = true;
+                }
             }
         }
         wc.scheduleAnimation();
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index e95d265..750fd50 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -128,7 +128,6 @@
         }
 
         ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
-        mService.openSurfaceTransaction();
         try {
             // Remove all deferred displays, tasks, and activities.
             root.handleCompleteDeferredRemoval();
@@ -163,6 +162,7 @@
                     dc.mLastContainsRunningSurfaceAnimator = false;
                     dc.enableHighFrameRate(false);
                 }
+                mTransaction.merge(dc.getPendingTransaction());
             }
 
             cancelAnimation();
@@ -196,8 +196,10 @@
             updateRunningExpensiveAnimationsLegacy();
         }
 
-        SurfaceControl.mergeToGlobalTransaction(mTransaction);
-        mService.closeSurfaceTransaction("WindowAnimator");
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction");
+        mTransaction.apply();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        mService.mWindowTracing.logState("WindowAnimator");
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
 
         mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2b77fff..e28262d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2988,12 +2988,23 @@
 
     /** Whether we can start change transition with this window and current display status. */
     boolean canStartChangeTransition() {
-        return !mWmService.mDisableTransitionAnimation && mDisplayContent != null
-                && getSurfaceControl() != null && !mDisplayContent.inTransition()
-                && isVisible() && isVisibleRequested() && okToAnimate()
-                // Pip animation will be handled by PipTaskOrganizer.
-                && !inPinnedWindowingMode() && getParent() != null
-                && !getParent().inPinnedWindowingMode();
+        if (mWmService.mDisableTransitionAnimation || !okToAnimate()) return false;
+
+        // Change transition only make sense as we go from "visible" to "visible".
+        if (mDisplayContent == null || getSurfaceControl() == null
+                || !isVisible() || !isVisibleRequested()) {
+            return false;
+        }
+
+        // Make sure display isn't a part of the transition already - needed for legacy transitions.
+        if (mDisplayContent.inTransition()) return false;
+
+        if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+            // Screenshots are turned off when PiP is undergoing changes.
+            return !inPinnedWindowingMode() && getParent() != null
+                    && !getParent().inPinnedWindowingMode();
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index b3a3650..89a70e5 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 mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
-
     final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
 
     final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 92bd00e..ae171a0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -46,6 +46,7 @@
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.inputmethod.ImeTracker;
+import android.window.ScreenCapture;
 
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
@@ -979,6 +980,14 @@
     public abstract SurfaceControl getA11yOverlayLayer(int displayId);
 
     /**
+     * Captures the entire display specified by the displayId using the args provided. If the args
+     * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
+     */
+    public abstract void captureDisplay(int displayId,
+                                        @Nullable ScreenCapture.CaptureArgs captureArgs,
+                                        ScreenCapture.ScreenCaptureListener listener);
+
+    /**
      * Device has a software navigation bar (separate from the status bar) on specific display.
      *
      * @param displayId the id of display to check if there is a software navigation bar.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 809e2d0..dd2b48b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1048,29 +1048,6 @@
 
     SystemPerformanceHinter mSystemPerformanceHinter;
 
-    void openSurfaceTransaction() {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
-            SurfaceControl.openTransaction();
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
-    /**
-     * Closes a surface transaction.
-     * @param where debug string indicating where the transaction originated
-     */
-    void closeSurfaceTransaction(String where) {
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
-            SurfaceControl.closeTransaction();
-            mWindowTracing.logState(where);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
             = new WindowManagerInternal.AppTransitionListener() {
@@ -1972,7 +1949,7 @@
         }
     }
 
-    void removeWindow(Session session, IWindow client) {
+    void removeClientToken(Session session, IBinder client) {
         synchronized (mGlobalLock) {
             WindowState win = windowForClientLocked(session, client, false);
             if (win != null) {
@@ -2350,8 +2327,8 @@
             boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                     && win.hasWallpaper();
             wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
-            if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) {
-                winAnimator.mSurfaceController.setSecure(win.isSecureLocked());
+            if ((flagChanges & FLAG_SECURE) != 0) {
+                win.setSecureLocked(win.isSecureLocked());
             }
 
             final boolean wasVisible = win.isVisible();
@@ -6261,9 +6238,11 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
-        doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        displayContent.requestDisplayUpdate(() -> {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
+            doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        });
     }
 
     private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
@@ -6299,7 +6278,6 @@
         mExitAnimId = exitAnim;
         mEnterAnimId = enterAnim;
 
-        displayContent.updateDisplayInfo();
         final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
                 ? overrideOriginalRotation
                 : displayContent.getDisplayInfo().rotation;
@@ -8529,6 +8507,12 @@
         }
 
         @Override
+        public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
+                                   ScreenCapture.ScreenCaptureListener listener) {
+            WindowManagerService.this.captureDisplay(displayId, captureArgs, listener);
+        }
+
+        @Override
         public boolean hasNavigationBar(int displayId) {
             return WindowManagerService.this.hasNavigationBar(displayId);
         }
@@ -8585,39 +8569,6 @@
         mAppFreezeListeners.remove(listener);
     }
 
-    /**
-     * WARNING: This interrupts surface updates, be careful! Don't
-     * execute within the transaction for longer than you would
-     * execute on an animation thread.
-     * WARNING: This method contains locks known to the State of California
-     * to cause Deadlocks and other conditions.
-     *
-     * Begins a surface transaction with which the AM can batch operations.
-     * All Surface updates performed by the WindowManager following this
-     * will not appear on screen until after the call to
-     * closeSurfaceTransaction.
-     *
-     * ActivityManager can use this to ensure multiple 'commands' will all
-     * be reflected in a single frame. For example when reparenting a window
-     * which was previously hidden due to it's parent properties, we may
-     * need to ensure it is hidden in the same frame that the properties
-     * from the new parent are inherited, otherwise it could be revealed
-     * mistakenly.
-     *
-     * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout
-     * with something like this but it seems that some existing cases of
-     * deferSurfaceLayout may be a little too broad, in particular the total
-     * enclosure of startActivityUnchecked which could run for quite some time.
-     */
-    void inSurfaceTransaction(Runnable exec) {
-        SurfaceControl.openTransaction();
-        try {
-            exec.run();
-        } finally {
-            SurfaceControl.closeTransaction();
-        }
-    }
-
     /** Called to inform window manager if non-Vr UI shoul be disabled or not. */
     public void disableNonVrUi(boolean disable) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 89d47bc..208df6c7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -63,6 +63,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 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;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -938,7 +939,14 @@
                     break;
                 }
                 final Task task = wc.asTask();
-                task.remove(true, "Applying remove task Hierarchy Op");
+
+                if (task.isLeafTask()) {
+                    mService.mTaskSupervisor
+                            .removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task"
+                                    + "-through-hierarchyOp");
+                } else {
+                    mService.mTaskSupervisor.removeRootTask(task);
+                }
                 break;
             }
             case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index a74a707..2b18f07 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -388,13 +388,22 @@
         final IApplicationThread thread = mThread;
         if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
                 && thread != null && mHasCachedConfiguration) {
-            final Configuration config;
+            final ConfigurationChangeItem configurationChangeItem;
             synchronized (mLastReportedConfiguration) {
-                config = new Configuration(mLastReportedConfiguration);
+                onConfigurationChangePreScheduled(mLastReportedConfiguration);
+                configurationChangeItem = ConfigurationChangeItem.obtain(
+                        mLastReportedConfiguration, mLastTopActivityDeviceId);
             }
             // Schedule immediately to make sure the app component (e.g. receiver, service) can get
             // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
-            scheduleConfigurationChange(thread, config);
+            try {
+                // No WM lock here.
+                mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+                        thread, configurationChangeItem);
+            } catch (Exception e) {
+                Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+                        + configurationChangeItem + " owner=" + mOwner, e);
+            }
         }
     }
 
@@ -1634,11 +1643,12 @@
             }
         }
 
-        scheduleConfigurationChange(thread, config);
+        onConfigurationChangePreScheduled(config);
+        scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
+                config, mLastTopActivityDeviceId));
     }
 
-    private void scheduleConfigurationChange(@NonNull IApplicationThread thread,
-            @NonNull Configuration config) {
+    private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
                 config);
         if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1646,8 +1656,6 @@
             Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
         }
         mHasCachedConfiguration = false;
-        scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
-                config, mLastTopActivityDeviceId));
     }
 
     @VisibleForTesting
@@ -1666,7 +1674,7 @@
     private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
             @NonNull ClientTransactionItem transactionItem) {
         try {
-            mAtm.getLifecycleManager().scheduleTransaction(thread, transactionItem);
+            mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
         } catch (Exception e) {
             Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem="
                     + transactionItem + " owner=" + mOwner, e);
@@ -1864,6 +1872,11 @@
     }
 
     @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+    public boolean isShowingUiWhileDozing() {
+        return this == mAtm.mVisibleDozeUiProcess;
+    }
+
+    @HotPath(caller = HotPath.OOM_ADJUSTMENT)
     public boolean isPreviousProcess() {
         return this == mAtm.mPreviousProcess;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5293292..e1f1f66 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -29,7 +29,6 @@
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.SurfaceControl.Transaction;
-import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -110,6 +109,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
@@ -178,6 +178,7 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
 import android.annotation.CallSuper;
@@ -1196,6 +1197,9 @@
         if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
             getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
         }
+        if (secureWindowState()) {
+            getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
+        }
     }
 
     void updateTrustedOverlay() {
@@ -2794,7 +2798,7 @@
                 clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
             }
             if (!isVisibleByPolicy()) {
-                mWinAnimator.hide(getGlobalTransaction(), "checkPolicyVisibilityChange");
+                mWinAnimator.hide(getPendingTransaction(), "checkPolicyVisibilityChange");
                 if (isFocused()) {
                     ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
                             "setAnimationLocked: setting mFocusMayChange true");
@@ -3277,7 +3281,9 @@
             // just kill it. And if it is a window of foreground activity, the activity can be
             // restarted automatically if needed.
             Slog.w(TAG, "Exception thrown during dispatchAppVisibility " + this, e);
-            android.os.Process.killProcess(mSession.mPid);
+            if (android.os.Process.getUidForPid(mSession.mPid) == mSession.mUid) {
+                android.os.Process.killProcess(mSession.mPid);
+            }
         }
     }
 
@@ -6041,4 +6047,25 @@
         // Cancel any draw requests during a sync.
         return mPrepareSyncSeqId > 0;
     }
+
+    void setSecureLocked(boolean isSecure) {
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName());
+        if (secureWindowState()) {
+            if (mSurfaceControl == null) {
+                return;
+            }
+            getPendingTransaction().setSecure(mSurfaceControl, isSecure);
+        } else {
+            if (mWinAnimator.mSurfaceController == null
+                    || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+                return;
+            }
+            getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+                    isSecure);
+        }
+        if (mDisplayContent != null) {
+            mDisplayContent.refreshImeSecureFlag(getSyncTransaction());
+        }
+        mWmService.scheduleAnimationLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 3aac816..44cd23d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -44,6 +44,7 @@
 import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
 import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
 import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.window.flags.Flags.secureWindowState;
 
 import android.content.Context;
 import android.graphics.PixelFormat;
@@ -286,8 +287,10 @@
         int flags = SurfaceControl.HIDDEN;
         final WindowManager.LayoutParams attrs = w.mAttrs;
 
-        if (w.isSecureLocked()) {
-            flags |= SurfaceControl.SECURE;
+        if (!secureWindowState()) {
+            if (w.isSecureLocked()) {
+                flags |= SurfaceControl.SECURE;
+            }
         }
 
         if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
@@ -488,13 +491,6 @@
         mSurfaceController.setOpaque(isOpaque);
     }
 
-    void setSecureLocked(boolean isSecure) {
-        if (mSurfaceController == null) {
-            return;
-        }
-        mSurfaceController.setSecure(isSecure);
-    }
-
     void setColorSpaceAgnosticLocked(boolean agnostic) {
         if (mSurfaceController == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 6c15c22..4456a94 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -20,12 +20,10 @@
 import static android.view.SurfaceControl.METADATA_OWNER_PID;
 import static android.view.SurfaceControl.METADATA_OWNER_UID;
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-import static android.view.SurfaceControl.getGlobalTransaction;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
@@ -148,35 +146,9 @@
         if (mSurfaceControl == null) {
             return;
         }
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
-        mService.openSurfaceTransaction();
-        try {
-            getGlobalTransaction().setOpaque(mSurfaceControl, isOpaque);
-        } finally {
-            mService.closeSurfaceTransaction("setOpaqueLocked");
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
-        }
-    }
 
-    void setSecure(boolean isSecure) {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, title);
-
-        if (mSurfaceControl == null) {
-            return;
-        }
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
-        mService.openSurfaceTransaction();
-        try {
-            getGlobalTransaction().setSecure(mSurfaceControl, isSecure);
-
-            final DisplayContent dc = mAnimator.mWin.mDisplayContent;
-            if (dc != null) {
-                dc.refreshImeSecureFlag(getGlobalTransaction());
-            }
-        } finally {
-            mService.closeSurfaceTransaction("setSecure");
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
-        }
+        mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+        mService.scheduleAnimationLocked();
     }
 
     void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
new file mode 100644
index 0000000..8c8f6a6
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
@@ -0,0 +1,78 @@
+/*
+ * 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.wm.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.DisplayInfo;
+
+/**
+ * Helper class to copy only subset of fields of DisplayInfo object or to perform
+ * comparison operation between DisplayInfo objects only with a subset of fields.
+ */
+public class DisplayInfoOverrides {
+
+    /**
+     * Set of DisplayInfo fields that are overridden in DisplayManager using values from
+     * WindowManager
+     */
+    public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> {
+        out.appWidth = source.appWidth;
+        out.appHeight = source.appHeight;
+        out.smallestNominalAppWidth = source.smallestNominalAppWidth;
+        out.smallestNominalAppHeight = source.smallestNominalAppHeight;
+        out.largestNominalAppWidth = source.largestNominalAppWidth;
+        out.largestNominalAppHeight = source.largestNominalAppHeight;
+        out.logicalWidth = source.logicalWidth;
+        out.logicalHeight = source.logicalHeight;
+        out.physicalXDpi = source.physicalXDpi;
+        out.physicalYDpi = source.physicalYDpi;
+        out.rotation = source.rotation;
+        out.displayCutout = source.displayCutout;
+        out.logicalDensityDpi = source.logicalDensityDpi;
+        out.roundedCorners = source.roundedCorners;
+        out.displayShape = source.displayShape;
+    };
+
+    /**
+     * Gets {@param base} DisplayInfo, overrides WindowManager-specific overrides using
+     * {@param override} and writes the result to {@param out}
+     */
+    public static void copyDisplayInfoFields(@NonNull DisplayInfo out,
+            @NonNull DisplayInfo base,
+            @Nullable DisplayInfo override,
+            @NonNull DisplayInfoFields fields) {
+        out.copyFrom(base);
+
+        if (override != null) {
+            fields.setFields(out, override);
+        }
+    }
+
+    /**
+     * Callback interface that allows to specify a subset of fields of DisplayInfo object
+     */
+    public interface DisplayInfoFields {
+        /**
+         * Copies a subset of fields from {@param source} to {@param out}
+         *
+         * @param out    resulting DisplayInfo object
+         * @param source source DisplayInfo to copy fields from
+         */
+        void setFields(@NonNull DisplayInfo out, @NonNull DisplayInfo source);
+    }
+}
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..7edf445 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -20,7 +20,6 @@
 
 #include <aidl/android/hardware/power/IPower.h>
 #include <android-base/stringprintf.h>
-#include <inttypes.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <powermanager/PowerHalController.h>
@@ -39,15 +38,6 @@
 
 namespace android {
 
-static struct {
-    jclass clazz{};
-    jfieldID workPeriodStartTimestampNanos{};
-    jfieldID actualTotalDurationNanos{};
-    jfieldID actualCpuDurationNanos{};
-    jfieldID actualGpuDurationNanos{};
-    jfieldID timestampNanos{};
-} gWorkDurationInfo;
-
 static power::PowerHalController gPowerHalController;
 static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
 static std::mutex gSessionMapLock;
@@ -190,26 +180,6 @@
     setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
 }
 
-static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
-                                            jobjectArray jWorkDurations) {
-    int size = env->GetArrayLength(jWorkDurations);
-    std::vector<WorkDuration> workDurations(size);
-    for (int i = 0; i < size; i++) {
-        jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
-        workDurations[i].workPeriodStartTimestampNanos =
-                env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos);
-        workDurations[i].durationNanos =
-                env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos);
-        workDurations[i].cpuDurationNanos =
-                env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos);
-        workDurations[i].gpuDurationNanos =
-                env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos);
-        workDurations[i].timeStampNanos =
-                env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos);
-    }
-    reportActualWorkDuration(session_ptr, workDurations);
-}
-
 // ----------------------------------------------------------------------------
 static const JNINativeMethod sHintManagerServiceMethods[] = {
         /* name, signature, funcPtr */
@@ -224,23 +194,9 @@
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
-        {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V",
-         (void*)nativeReportActualWorkDuration2},
 };
 
 int register_android_server_HintManagerService(JNIEnv* env) {
-    gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration");
-    gWorkDurationInfo.workPeriodStartTimestampNanos =
-            env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J");
-    gWorkDurationInfo.actualTotalDurationNanos =
-            env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J");
-    gWorkDurationInfo.actualCpuDurationNanos =
-            env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J");
-    gWorkDurationInfo.actualGpuDurationNanos =
-            env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J");
-    gWorkDurationInfo.timestampNanos =
-            env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J");
-
     return jniRegisterNativeMethods(env,
                                     "com/android/server/power/hint/"
                                     "HintManagerService$NativeWrapper",
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 cca4261..c625b1e 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -159,6 +159,12 @@
                 <xs:element type="usiVersion" name="usiVersion">
                     <xs:annotation name="final"/>
                 </xs:element>
+                <!-- Maximum screen brightness setting when screen brightness capped in
+                Wear Bedtime mode. This must be a non-negative decimal within the range defined by
+                the first and the last brightness value in screenBrightnessMap. -->
+                <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode">
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -586,42 +592,39 @@
                         minOccurs="0" maxOccurs="1">
                 <xs:annotation name="final"/>
             </xs:element>
-            <!-- Sets the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-            <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
+            <!-- Sets the brightness mapping of the desired screen brightness to the corresponding
+            lux for the current display -->
+            <xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping"
                         minOccurs="0" maxOccurs="1">
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
     </xs:complexType>
 
-    <!-- Represents the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-    <xs:complexType name="displayBrightnessMapping">
-        <xs:sequence>
-            <!-- Sets the list of display brightness points, each representing the desired screen
-            brightness in nits to the corresponding lux for the current display
+    <!-- Sets the list of display brightness points, each representing the desired screen brightness
+    in a certain lux environment.
 
-            The N entries of this array define N + 1 control points as follows:
-            (1-based arrays)
+    The first value of each point is the lux value and the second value is the brightness value.
 
-            Point 1:            (0, nits[1]):             currentLux <= 0
-            Point 2:     (lux[1], nits[2]):       0 < currentLux <= lux[1]
-            Point 3:     (lux[2], nits[3]):  lux[2] < currentLux <= lux[3]
-            ...
-            Point N+1: (lux[N], nits[N+1]):            lux[N] < currentLux
+    The first lux value must be 0.
 
-            The control points must be strictly increasing. Each control point
-            corresponds to an entry in the brightness backlight values arrays.
-            For example, if currentLux == lux[1] (first element of the levels array)
-            then the brightness will be determined by nits[2] (second element
-            of the brightness values array).
-            -->
-            <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
-                        minOccurs="1" maxOccurs="unbounded">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
+    The control points must be strictly increasing.
+
+    Example: if currentLux == the second lux value in the mapping then the brightness will be
+    determined by the second brightness value in the mapping. Spline interpolation is used
+    to determine the auto-brightness values for lux levels between these control points.
+
+    The brightness values must be non-negative decimals within the range between the first and
+    the last brightness values in screenBrightnessMap.
+
+    This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues
+    defined in the config XML resource.
+    -->
+    <xs:complexType name="luxToBrightnessMapping">
+        <xs:element name="map" type="nonNegativeFloatToFloatMap">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
     </xs:complexType>
 
     <!-- Represents a point in the display brightness mapping, representing the lux level from the
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f767291..8c8c123 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -7,14 +7,14 @@
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
-    method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
     method public boolean getEnabled();
+    method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping();
     method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
-    method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
     method public void setEnabled(boolean);
+    method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping);
   }
 
   public class BlockingZoneConfig {
@@ -78,11 +78,6 @@
     method public java.util.List<com.android.server.display.config.Density> getDensity();
   }
 
-  public class DisplayBrightnessMapping {
-    ctor public DisplayBrightnessMapping();
-    method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
-  }
-
   public class DisplayBrightnessPoint {
     ctor public DisplayBrightnessPoint();
     method public final java.math.BigInteger getLux();
@@ -110,6 +105,7 @@
     method public final com.android.server.display.config.SensorDetails getProxSensor();
     method public com.android.server.display.config.DisplayQuirks getQuirks();
     method public com.android.server.display.config.RefreshRateConfigs getRefreshRate();
+    method public final java.math.BigDecimal getScreenBrightnessCapForWearBedtimeMode();
     method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault();
     method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap();
     method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxIdleMillis();
@@ -143,6 +139,7 @@
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
     method public void setQuirks(com.android.server.display.config.DisplayQuirks);
     method public void setRefreshRate(com.android.server.display.config.RefreshRateConfigs);
+    method public final void setScreenBrightnessCapForWearBedtimeMode(java.math.BigDecimal);
     method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal);
     method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap);
     method public final void setScreenBrightnessRampDecreaseMaxIdleMillis(java.math.BigInteger);
@@ -220,6 +217,12 @@
     method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
   }
 
+  public class LuxToBrightnessMapping {
+    ctor public LuxToBrightnessMapping();
+    method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+    method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+  }
+
   public class NitsMap {
     ctor public NitsMap();
     method public String getInterpolation();
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1988bb6..da44aac 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -23,12 +23,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.credentials.CredentialProviderInfo;
+import android.credentials.flags.Flags;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -94,6 +96,9 @@
 
     private final Set<ComponentName> mEnabledProviders;
 
+    private final RequestSessionDeathRecipient mDeathRecipient =
+            new RequestSessionDeathRecipient();
+
     protected PendingIntent mPendingIntent;
 
     @NonNull
@@ -141,11 +146,26 @@
         mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted,
                 mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
         setCancellationListener();
+        if (Flags.clearSessionEnabled()) {
+            setUpClientCallbackListener();
+        }
+    }
+
+    private void setUpClientCallbackListener() {
+        if (mClientCallback != null && mClientCallback instanceof IInterface) {
+            IInterface callback = (IInterface) mClientCallback;
+            try {
+                callback.asBinder().linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, e.getMessage());
+            }
+        }
     }
 
     private void setCancellationListener() {
         mCancellationSignal.setOnCancelListener(
                 () -> {
+                    Slog.d(TAG, "Cancellation invoked from the client - clearing session");
                     boolean isUiActive = maybeCancelUi();
                     finishSession(!isUiActive);
                 }
@@ -168,6 +188,17 @@
         return false;
     }
 
+    private boolean isUiWaitingForData() {
+        // Technically, the status can also be IN_PROGRESS when the user has made a selection
+        // so this an over estimation, but safe to do so as it is used for cancellation
+        // propagation to the provider in a very narrow time frame. If provider has
+        // already responded, cancellation is not an issue as the cancellation listener
+        // is independent of the service binding.
+        // TODO(b/313512500): Do not propagate cancelation if provider has responded in
+        // query phase.
+        return mCredentialManagerUi.getStatus() == CredentialManagerUi.UiStatus.IN_PROGRESS;
+    }
+
     public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
             RemoteCredentialService remoteCredentialService);
 
@@ -373,4 +404,12 @@
         return chosenProviderSession != null && chosenProviderSession.mProviderInfo != null
                 && chosenProviderSession.mProviderInfo.isPrimary();
     }
+
+    private class RequestSessionDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            Slog.d(TAG, "Client binder died - clearing session");
+            finishSession(isUiWaitingForData());
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9b62a2c..e0a2f30 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18182,7 +18182,8 @@
         private static boolean hasAccountFeatures(AccountManager am, Account account,
                 String[] features) {
             try {
-                return am.hasFeatures(account, features, null, null).getResult();
+                return am.hasFeatures(account, features, null, null)
+                        .getResult(30, TimeUnit.SECONDS);
             } catch (Exception e) {
                 Slogf.w(LOG_TAG, "Failed to get account feature", e);
                 return false;
diff --git a/services/foldables/devicestateprovider/Android.bp b/services/foldables/devicestateprovider/Android.bp
index 34737ef..56daea7 100644
--- a/services/foldables/devicestateprovider/Android.bp
+++ b/services/foldables/devicestateprovider/Android.bp
@@ -5,9 +5,12 @@
 java_library {
     name: "foldable-device-state-provider",
     srcs: [
-        "src/**/*.java"
+        "src/**/*.java",
     ],
     libs: [
         "services",
     ],
+    static_libs: [
+        "device_state_flags_lib",
+    ],
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index aea46d1..4c487a7 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -21,6 +21,7 @@
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.TYPE_EXTERNAL;
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -33,11 +34,14 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
-import android.os.PowerManager;
 import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
@@ -45,24 +49,26 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.policy.feature.flags.FeatureFlags;
+import com.android.server.policy.feature.flags.FeatureFlagsImpl;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
 import java.util.function.BooleanSupplier;
-import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Device state provider for foldable devices.
- *
+ * <p>
  * It is an implementation of {@link DeviceStateProvider} tailored specifically for
  * foldable devices and allows simple callback-based configuration with hall sensor
  * and hinge angle sensor values.
  */
 public final class FoldableDeviceStateProvider implements DeviceStateProvider,
         SensorEventListener, PowerManager.OnThermalStatusChangedListener,
-       DisplayManager.DisplayListener  {
+        DisplayManager.DisplayListener {
 
     private static final String TAG = "FoldableDeviceStateProvider";
     private static final boolean DEBUG = false;
@@ -77,9 +83,17 @@
     // are met for the device to be in the state.
     private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>();
 
+    // Map of state identifier to a boolean supplier that returns true when the device state has all
+    // the conditions needed for availability.
+    private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray();
+
     private final Sensor mHingeAngleSensor;
     private final DisplayManager mDisplayManager;
     private final Sensor mHallSensor;
+    private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true;
 
     @Nullable
     @GuardedBy("mLock")
@@ -99,7 +113,23 @@
     @GuardedBy("mLock")
     private boolean mPowerSaveModeEnabled;
 
-    public FoldableDeviceStateProvider(@NonNull Context context,
+    private final boolean mIsDualDisplayBlockingEnabled;
+
+    public FoldableDeviceStateProvider(
+            @NonNull Context context,
+            @NonNull SensorManager sensorManager,
+            @NonNull Sensor hingeAngleSensor,
+            @NonNull Sensor hallSensor,
+            @NonNull DisplayManager displayManager,
+            @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+        this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor,
+                displayManager, deviceStateConfigurations);
+    }
+
+    @VisibleForTesting
+    public FoldableDeviceStateProvider(
+            @NonNull FeatureFlags featureFlags,
+            @NonNull Context context,
             @NonNull SensorManager sensorManager,
             @NonNull Sensor hingeAngleSensor,
             @NonNull Sensor hallSensor,
@@ -112,6 +142,7 @@
         mHingeAngleSensor = hingeAngleSensor;
         mHallSensor = hallSensor;
         mDisplayManager = displayManager;
+        mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
 
         sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
         sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST);
@@ -121,20 +152,15 @@
             final DeviceStateConfiguration configuration = deviceStateConfigurations[i];
             mOrderedStates[i] = configuration.mDeviceState;
 
-            if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
-                throw new IllegalArgumentException("Device state configurations must have unique"
-                        + " device state identifiers, found duplicated identifier: " +
-                        configuration.mDeviceState.getIdentifier());
-            }
-
-            mStateConditions.put(configuration.mDeviceState.getIdentifier(), () ->
-                    configuration.mPredicate.apply(this));
+            assertUniqueDeviceStateIdentifier(configuration);
+            initialiseStateConditions(configuration);
+            initialiseStateAvailabilityConditions(configuration);
         }
 
+        Handler handler = new Handler(Looper.getMainLooper());
         mDisplayManager.registerDisplayListener(
                 /* listener = */ this,
-                /* handler= */ null,
-                /* eventsMask= */ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+                /* handler= */ handler);
 
         Arrays.sort(mOrderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
 
@@ -167,6 +193,24 @@
         }
     }
 
+    private void assertUniqueDeviceStateIdentifier(DeviceStateConfiguration configuration) {
+        if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
+            throw new IllegalArgumentException("Device state configurations must have unique"
+                    + " device state identifiers, found duplicated identifier: "
+                    + configuration.mDeviceState.getIdentifier());
+        }
+    }
+
+    private void initialiseStateConditions(DeviceStateConfiguration configuration) {
+        mStateConditions.put(configuration.mDeviceState.getIdentifier(), () ->
+                configuration.mActiveStatePredicate.test(this));
+    }
+
+    private void initialiseStateAvailabilityConditions(DeviceStateConfiguration configuration) {
+            mStateAvailabilityConditions.put(configuration.mDeviceState.getIdentifier(), () ->
+                    configuration.mAvailabilityPredicate.test(this));
+    }
+
     @Override
     public void setListener(Listener listener) {
         synchronized (mLock) {
@@ -189,16 +233,9 @@
             }
             listener = mListener;
             for (DeviceState deviceState : mOrderedStates) {
-                if (isThermalStatusCriticalOrAbove(mThermalStatus)
-                        && deviceState.hasFlag(
-                        DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
-                    continue;
+                if (isStateSupported(deviceState)) {
+                    supportedStates.add(deviceState);
                 }
-                if (mPowerSaveModeEnabled && deviceState.hasFlag(
-                        DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
-                    continue;
-                }
-                supportedStates.add(deviceState);
             }
         }
 
@@ -206,6 +243,26 @@
                 supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
     }
 
+    @GuardedBy("mLock")
+    private boolean isStateSupported(DeviceState deviceState) {
+        if (isThermalStatusCriticalOrAbove(mThermalStatus)
+                && deviceState.hasFlag(
+                DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+            return false;
+        }
+        if (mPowerSaveModeEnabled && deviceState.hasFlag(
+                DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+            return false;
+        }
+        if (mIsDualDisplayBlockingEnabled
+                && mStateAvailabilityConditions.contains(deviceState.getIdentifier())) {
+            return mStateAvailabilityConditions
+                    .get(deviceState.getIdentifier())
+                    .getAsBoolean();
+        }
+        return true;
+    }
+
     /** Computes the current device state and notifies the listener of a change, if needed. */
     void notifyDeviceStateChangedIfNeeded() {
         int stateToReport = INVALID_DEVICE_STATE;
@@ -294,7 +351,7 @@
     private void dumpSensorValues() {
         Slog.i(TAG, "Sensor values:");
         dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent);
-        dumpSensorValues("Hinge Angle Sensor",mHingeAngleSensor, mLastHingeAngleSensorEvent);
+        dumpSensorValues("Hinge Angle Sensor", mHingeAngleSensor, mLastHingeAngleSensorEvent);
         Slog.i(TAG, "isScreenOn: " + isScreenOn());
     }
 
@@ -307,12 +364,35 @@
 
     @Override
     public void onDisplayAdded(int displayId) {
+        // TODO(b/312397262): consider virtual displays cases
+        synchronized (mLock) {
+            if (mIsDualDisplayBlockingEnabled
+                    && !mExternalDisplaysConnected.get(displayId, false)
+                    && mDisplayManager.getDisplay(displayId).getType() == TYPE_EXTERNAL) {
+                mExternalDisplaysConnected.put(displayId, true);
 
+                // Only update the supported state when going from 0 external display to 1
+                if (mExternalDisplaysConnected.size() == 1) {
+                    notifySupportedStatesChanged(
+                            SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED);
+                }
+            }
+        }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
+        synchronized (mLock) {
+            if (mIsDualDisplayBlockingEnabled && mExternalDisplaysConnected.get(displayId, false)) {
+                mExternalDisplaysConnected.delete(displayId);
 
+                // Only update the supported states when going from 1 external display to 0
+                if (mExternalDisplaysConnected.size() == 0) {
+                    notifySupportedStatesChanged(
+                            SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED);
+                }
+            }
+        }
     }
 
     @Override
@@ -338,48 +418,71 @@
      */
     public static class DeviceStateConfiguration {
         private final DeviceState mDeviceState;
-        private final Function<FoldableDeviceStateProvider, Boolean> mPredicate;
+        private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
+        private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
 
-        private DeviceStateConfiguration(DeviceState deviceState,
-                Function<FoldableDeviceStateProvider, Boolean> predicate) {
+        private DeviceStateConfiguration(
+                @NonNull DeviceState deviceState,
+                @NonNull Predicate<FoldableDeviceStateProvider> predicate) {
+            this(deviceState, predicate, ALLOWED);
+        }
+
+        private DeviceStateConfiguration(
+                @NonNull DeviceState deviceState,
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+                @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
+
             mDeviceState = deviceState;
-            mPredicate = predicate;
+            mActiveStatePredicate = activeStatePredicate;
+            mAvailabilityPredicate = availabilityPredicate;
         }
 
         public static DeviceStateConfiguration createConfig(
                 @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
                 @NonNull String name,
                 @DeviceState.DeviceStateFlags int flags,
-                Function<FoldableDeviceStateProvider, Boolean> predicate
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
         ) {
             return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
-                    predicate);
+                    activeStatePredicate);
         }
 
         public static DeviceStateConfiguration createConfig(
                 @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
                 @NonNull String name,
-                Function<FoldableDeviceStateProvider, Boolean> predicate
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
         ) {
             return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0),
-                    predicate);
+                    activeStatePredicate);
+        }
+
+        /** Create a configuration with availability predicate **/
+        public static DeviceStateConfiguration createConfig(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
+                @NonNull String name,
+                @DeviceState.DeviceStateFlags int flags,
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+                @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate
+        ) {
+            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
+                    activeStatePredicate, availabilityPredicate);
         }
 
         /**
          * Creates a device state configuration for a closed tent-mode aware state.
-         *
+         * <p>
          * During tent mode:
          * - The inner display is OFF
          * - The outer display is ON
          * - The device is partially unfolded (left and right edges could be on the table)
          * In this mode the device the device so it could be used in a posture where both left
          * and right edges of the unfolded device are on the table.
-         *
+         * <p>
          * The predicate returns false after the hinge angle reaches
          * {@code tentModeSwitchAngleDegrees}. Then it switches back only when the hinge angle
          * becomes less than {@code maxClosedAngleDegrees}. Hinge angle is 0 degrees when the device
          * is fully closed and 180 degrees when it is fully unfolded.
-         *
+         * <p>
          * For example, when tentModeSwitchAngleDegrees = 90 and maxClosedAngleDegrees = 5 degrees:
          *  - when unfolding the device from fully closed posture (last state == closed or it is
          *    undefined yet) this state will become not matching after reaching the angle
@@ -435,6 +538,15 @@
     }
 
     /**
+     * @return Whether there is an external connected display.
+     */
+    public boolean hasNoConnectedExternalDisplay() {
+        synchronized (mLock) {
+            return mExternalDisplaysConnected.size() == 0;
+        }
+    }
+
+    /**
      * @return Whether the screen is on.
      */
     public boolean isScreenOn() {
@@ -442,6 +554,7 @@
             return mIsScreenOn;
         }
     }
+
     /**
      * @return current hinge angle value of a foldable device
      */
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
index 5f2cf3c..5968b63 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
@@ -33,6 +33,10 @@
 import com.android.server.devicestate.DeviceStatePolicy;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.feature.flags.FeatureFlags;
+import com.android.server.policy.feature.flags.FeatureFlagsImpl;
+
+import java.util.function.Predicate;
 
 /**
  * Device state policy for a foldable device that supports tent mode: a mode when the device
@@ -55,6 +59,10 @@
 
     private final DeviceStateProvider mProvider;
 
+    private final boolean mIsDualDisplayBlockingEnabled;
+    private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true;
+    private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false;
+
     /**
      * Creates TentModeDeviceStatePolicy
      *
@@ -67,6 +75,12 @@
      */
     public TentModeDeviceStatePolicy(@NonNull Context context,
             @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) {
+        this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees);
+    }
+
+    public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context,
+                                     @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor,
+                                     int closeAngleDegrees) {
         super(context);
 
         final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
@@ -74,8 +88,10 @@
 
         final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees);
 
-        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
-                hallSensor, displayManager, configuration);
+        mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
+
+        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
+                hingeAngleSensor, hallSensor, displayManager, configuration);
     }
 
     private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) {
@@ -83,24 +99,27 @@
                 createClosedConfiguration(closeAngleDegrees),
                 createConfig(DEVICE_STATE_HALF_OPENED,
                         /* name= */ "HALF_OPENED",
-                        (provider) -> {
+                        /* activeStatePredicate= */ (provider) -> {
                             final float hingeAngle = provider.getHingeAngle();
                             return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
                                     && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
                         }),
                 createConfig(DEVICE_STATE_OPENED,
                         /* name= */ "OPENED",
-                        (provider) -> true),
+                        /* activeStatePredicate= */ ALLOWED),
                 createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
                         /* name= */ "REAR_DISPLAY_STATE",
                         /* flags= */ FLAG_EMULATED_ONLY,
-                        (provider) -> false),
+                        /* activeStatePredicate= */ NOT_ALLOWED),
                 createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT,
                         /* name= */ "CONCURRENT_INNER_DEFAULT",
                         /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP
                                 | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
                                 | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
-                        (provider) -> false)
+                        /* activeStatePredicate= */ NOT_ALLOWED,
+                        /* availabilityPredicate= */
+                        provider -> !mIsDualDisplayBlockingEnabled
+                                || provider.hasNoConnectedExternalDisplay())
         };
     }
 
@@ -111,7 +130,7 @@
                     DEVICE_STATE_CLOSED,
                     /* name= */ "CLOSED",
                     /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                    (provider) -> {
+                    /* activeStatePredicate= */ (provider) -> {
                         final float hingeAngle = provider.getHingeAngle();
                         return hingeAngle <= closeAngleDegrees;
                     }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
new file mode 100644
index 0000000..6ad8d79
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "device_state_flags",
+    package: "com.android.server.policy.feature.flags",
+    srcs: [
+        "device_state_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "device_state_flags_lib",
+    aconfig_declarations: "device_state_flags",
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
new file mode 100644
index 0000000..47c2a1b
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.policy.feature.flags"
+
+flag {
+    name: "enable_dual_display_blocking"
+    namespace: "display_manager"
+    description: "Feature flag for dual display blocking"
+    bug: "278667199"
+}
\ No newline at end of file
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index 8fa4ce5..ddf4a08 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -17,18 +17,21 @@
 package com.android.server.policy;
 
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
-import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.STATE_OFF;
-import static android.view.Display.STATE_ON;
-
 import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -36,12 +39,11 @@
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -51,20 +53,21 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputSensorInfo;
-import android.os.PowerManager;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
 
 import com.android.server.devicestate.DeviceState;
-import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.devicestate.DeviceStateProvider.Listener;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
+import com.android.server.policy.feature.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -95,10 +98,16 @@
     @Mock
     private DisplayManager mDisplayManager;
     private FoldableDeviceStateProvider mProvider;
+    @Mock
+    private Display mDefaultDisplay;
+    @Mock
+    private Display mExternalDisplay;
 
+    private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
 
         mHallSensor = new Sensor(mInputSensorInfo);
         mHingeAngleSensor = new Sensor(mInputSensorInfo);
@@ -473,6 +482,133 @@
         assertThat(mProvider.isScreenOn()).isFalse();
     }
 
+    @Test
+    public void test_dualScreenDisabledWhenExternalScreenIsConnected() throws Exception {
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
+        when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
+
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+                        (c) -> c.getHingeAngle() < 5f),
+                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                        (c) -> c.getHingeAngle() < 180f),
+                createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
+                        (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+
+        clearInvocations(listener);
+
+        when(mDisplayManager.getDisplays())
+                .thenReturn(new Display[]{mDefaultDisplay, mExternalDisplay});
+        when(mDisplayManager.getDisplay(1)).thenReturn(mExternalDisplay);
+        when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL);
+
+        // The DUAL_DISPLAY state should be disabled.
+        mProvider.onDisplayAdded(1);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED));
+        assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */)}).inOrder();
+        clearInvocations(listener);
+
+        // The DUAL_DISPLAY state should be re-enabled.
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
+        mProvider.onDisplayRemoved(1);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED));
+        assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+    }
+
+    @Test
+    public void test_notifySupportedStatesChangedCalledOnlyOnInitialExternalScreenAddition() {
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
+        when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
+
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+                        (c) -> c.getHingeAngle() < 5f),
+                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                        (c) -> c.getHingeAngle() < 180f),
+                createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
+                        (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+
+        clearInvocations(listener);
+
+        addExternalDisplay(1);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED));
+        addExternalDisplay(2);
+        addExternalDisplay(3);
+        addExternalDisplay(4);
+        verify(listener, times(1))
+                .onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED));
+    }
+
+    @Test
+    public void hasNoConnectedDisplay_afterExternalDisplayAdded_returnsFalse() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE",
+                        /* flags= */0, (c) -> true,
+                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+        );
+
+        addExternalDisplay(/* displayId */ 1);
+
+        assertThat(mProvider.hasNoConnectedExternalDisplay()).isFalse();
+    }
+
+    @Test
+    public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE",
+                        /* flags= */0, (c) -> true,
+                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+        );
+
+        addExternalDisplay(/* displayId */ 1);
+        mProvider.onDisplayRemoved(1);
+
+        assertThat(mProvider.hasNoConnectedExternalDisplay()).isTrue();
+    }
+    private void addExternalDisplay(int displayId) {
+        when(mDisplayManager.getDisplay(displayId)).thenReturn(mExternalDisplay);
+        when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL);
+        mProvider.onDisplayAdded(displayId);
+    }
     private void setScreenOn(boolean isOn) {
         Display mockDisplay = mock(Display.class);
         int state = isOn ? STATE_ON : STATE_OFF;
@@ -508,12 +644,11 @@
     }
 
     private void createProvider(DeviceStateConfiguration... configurations) {
-        mProvider = new FoldableDeviceStateProvider(mContext, mSensorManager, mHingeAngleSensor,
-                mHallSensor, mDisplayManager, configurations);
+        mProvider = new FoldableDeviceStateProvider(mFakeFeatureFlags, mContext, mSensorManager,
+                mHingeAngleSensor, mHallSensor, mDisplayManager, configurations);
         verify(mDisplayManager)
                 .registerDisplayListener(
                         mDisplayListenerCaptor.capture(),
-                        nullable(Handler.class),
-                        anyLong());
+                        nullable(Handler.class));
     }
 }
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
index e464038..487c992 100644
--- a/services/permission/OWNERS
+++ b/services/permission/OWNERS
@@ -1,5 +1,3 @@
 #Bug component: 137825
 
-joecastro@google.com
-ntmyren@google.com
-zhanghai@google.com
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 26ea9d2..8f464d4 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -71,7 +71,7 @@
         // Not implemented because upgrades are handled automatically.
     }
 
-    override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+    override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
         return opNameMapToOpSparseArray(getUidModes(uid))
     }
 
@@ -79,7 +79,7 @@
         return opNameMapToOpSparseArray(getPackageModes(packageName, userId))
     }
 
-    override fun getUidMode(uid: Int, op: Int): Int {
+    override fun getUidMode(uid: Int, persistentDeviceId: String, op: Int): Int {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
@@ -92,7 +92,7 @@
         return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
     }
 
-    override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+    override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
@@ -145,22 +145,12 @@
             opSparseArray
         }
 
-    override fun areUidModesDefault(uid: Int): Boolean {
-        val modes = getUidModes(uid)
-        return modes == null || modes.isEmpty()
-    }
-
-    override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
-        val modes = service.getState { getPackageModes(packageName, userId) }
-        return modes == null || modes.isEmpty()
-    }
-
     override fun clearAllModes() {
         // We don't need to implement this because it's only called in AppOpsService#readState
         // and we have our own persistence.
     }
 
-    override fun getForegroundOps(uid: Int): SparseBooleanArray {
+    override fun getForegroundOps(uid: Int, persistentDeviceId: String): SparseBooleanArray {
         return SparseBooleanArray().apply {
             getUidModes(uid)?.forEachIndexed { _, op, mode ->
                 if (mode == AppOpsManager.MODE_FOREGROUND) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 010604f..02032c7 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1706,7 +1706,7 @@
     }
 
     /** Listener for permission flags changes. */
-    abstract class OnPermissionFlagsChangedListener {
+    interface OnPermissionFlagsChangedListener {
         /**
          * Called when a permission flags change has been made to the upcoming new state.
          *
@@ -1714,7 +1714,7 @@
          * and only call external code after [onStateMutated] when the new state has actually become
          * the current state visible to external code.
          */
-        abstract fun onPermissionFlagsChanged(
+        fun onPermissionFlagsChanged(
             appId: Int,
             userId: Int,
             permissionName: String,
@@ -1727,6 +1727,6 @@
          *
          * Implementations should keep this method fast to avoid stalling the locked state mutation.
          */
-        abstract fun onStateMutated()
+        fun onStateMutated()
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 7db09f9..bb68bc5 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -263,10 +263,6 @@
         synchronized(listenersLock) { listeners = listeners + listener }
     }
 
-    fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) { listeners = listeners - listener }
-    }
-
     private fun isDeviceAwarePermission(permissionName: String): Boolean =
         DEVICE_AWARE_PERMISSIONS.contains(permissionName)
 
@@ -283,11 +279,8 @@
             }
     }
 
-    /**
-     * TODO: b/289355341 - implement listener for permission changes Listener for permission flags
-     *   changes.
-     */
-    abstract class OnDevicePermissionFlagsChangedListener {
+    /** Listener for permission flags changes. */
+    interface OnDevicePermissionFlagsChangedListener {
         /**
          * Called when a permission flags change has been made to the upcoming new state.
          *
@@ -295,7 +288,7 @@
          * and only call external code after [onStateMutated] when the new state has actually become
          * the current state visible to external code.
          */
-        abstract fun onDevicePermissionFlagsChanged(
+        fun onDevicePermissionFlagsChanged(
             appId: Int,
             userId: Int,
             deviceId: String,
@@ -309,6 +302,6 @@
          *
          * Implementations should keep this method fast to avoid stalling the locked state mutation.
          */
-        abstract fun onStateMutated()
+        fun onStateMutated()
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index ab3d78c..7c53950 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -19,6 +19,7 @@
 import android.Manifest
 import android.app.ActivityManager
 import android.app.AppOpsManager
+import android.companion.virtual.VirtualDeviceManager
 import android.compat.annotation.ChangeId
 import android.compat.annotation.EnabledAfter
 import android.content.Context
@@ -169,6 +170,7 @@
         onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
         onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
         policy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener)
+        devicePolicy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener)
     }
 
     override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
@@ -2173,9 +2175,9 @@
 
                     userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
                         _,
-                        deviceId,
+                        persistentDeviceId,
                         devicePermissionFlags ->
-                        println("Permissions (Device $deviceId):")
+                        println("Permissions (Device $persistentDeviceId):")
                         withIndent {
                             devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
                                 val isGranted = PermissionFlags.isPermissionGranted(flags)
@@ -2616,10 +2618,11 @@
 
     /** Callback invoked when interesting actions have been taken on a permission. */
     private inner class OnPermissionFlagsChangedListener :
-        AppIdPermissionPolicy.OnPermissionFlagsChangedListener() {
+        AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+        DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
         private var isPermissionFlagsChanged = false
 
-        private val runtimePermissionChangedUids = MutableIntSet()
+        private val runtimePermissionChangedUidDevices = MutableIntMap<MutableSet<String>>()
         // Mapping from UID to whether only notifications permissions are revoked.
         private val runtimePermissionRevokedUids = SparseBooleanArray()
         private val gidsChangedUids = MutableIntSet()
@@ -2642,6 +2645,24 @@
             oldFlags: Int,
             newFlags: Int
         ) {
+            onDevicePermissionFlagsChanged(
+                appId,
+                userId,
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+                permissionName,
+                oldFlags,
+                newFlags
+            )
+        }
+
+        override fun onDevicePermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            persistentDeviceId: String,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        ) {
             isPermissionFlagsChanged = true
 
             val uid = UserHandle.getUid(userId, appId)
@@ -2655,12 +2676,13 @@
                 // permission flags have changed for a non-runtime permission, now we no longer do
                 // that because permission flags are only for runtime permissions and the listeners
                 // aren't being notified of non-runtime permission grant state changes anyway.
-                runtimePermissionChangedUids += uid
                 if (wasPermissionGranted && !isPermissionGranted) {
                     runtimePermissionRevokedUids[uid] =
                         permissionName in NOTIFICATIONS_PERMISSIONS &&
                             runtimePermissionRevokedUids.get(uid, true)
                 }
+                runtimePermissionChangedUidDevices
+                    .getOrPut(uid) { mutableSetOf() } += persistentDeviceId
             }
 
             if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) {
@@ -2674,10 +2696,12 @@
                 isPermissionFlagsChanged = false
             }
 
-            runtimePermissionChangedUids.forEachIndexed { _, uid ->
-                onPermissionsChangeListeners.onPermissionsChanged(uid)
+            runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds ->
+                persistentDeviceIds.forEach { persistentDeviceId ->
+                    onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId)
+                }
             }
-            runtimePermissionChangedUids.clear()
+            runtimePermissionChangedUidDevices.clear()
 
             if (!isKillRuntimePermissionRevokedUidsSkipped) {
                 val reason =
@@ -2749,15 +2773,16 @@
             when (msg.what) {
                 MSG_ON_PERMISSIONS_CHANGED -> {
                     val uid = msg.arg1
-                    handleOnPermissionsChanged(uid)
+                    val persistentDeviceId = msg.obj as String
+                    handleOnPermissionsChanged(uid, persistentDeviceId)
                 }
             }
         }
 
-        private fun handleOnPermissionsChanged(uid: Int) {
+        private fun handleOnPermissionsChanged(uid: Int, persistentDeviceId: String) {
             listeners.broadcast { listener ->
                 try {
-                    listener.onPermissionsChanged(uid)
+                    listener.onPermissionsChanged(uid, persistentDeviceId)
                 } catch (e: RemoteException) {
                     Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
                 }
@@ -2772,9 +2797,10 @@
             listeners.unregister(listener)
         }
 
-        fun onPermissionsChanged(uid: Int) {
+        fun onPermissionsChanged(uid: Int, persistentDeviceId: String) {
             if (listeners.registeredCallbackCount > 0) {
-                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget()
+                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId)
+                    .sendToTarget()
             }
         }
 
diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
index 4e46836..d62da1a 100644
--- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
+++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
@@ -57,11 +57,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 import com.android.server.LocalServices;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowUserManager;
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index 6c24d6d..820628c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -21,8 +21,8 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
-        <option name="test-file-name" value="FrameworksImeTests.apk" />
         <option name="test-file-name" value="SimpleTestIme.apk" />
+        <option name="test-file-name" value="FrameworksImeTests.apk" />
     </target_preparer>
 
     <option name="test-tag" value="FrameworksImeTests" />
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index b63a58a..2134278 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
 import android.content.Context;
@@ -48,6 +49,7 @@
 
 import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
 import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -635,6 +637,82 @@
                 .getRootWindowInsets().getInsetsIgnoringVisibility(captionBar()));
     }
 
+    /**
+     * This checks that trying to show and hide the navigation bar takes effect
+     * when the IME does draw the IME navigation bar.
+     */
+    @Test
+    public void testShowHideImeNavigationBar_doesDrawImeNavBar() throws Exception {
+        boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+                .hasNavigationBar(mInputMethodService.getDisplayId());
+        assumeTrue("Must have a navigation bar", hasNavigationBar);
+
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Show IME
+        verifyInputViewStatusOnMainSync(
+                () -> {
+                    mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+                            InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+                                    | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+                    );
+                    assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue();
+                },
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
+
+        // Try to hide IME nav bar
+        mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow()
+                .getInsetsController().hide(captionBar()));
+        mInstrumentation.waitForIdleSync();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
+
+        // Try to show IME nav bar
+        mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow()
+                .getInsetsController().show(captionBar()));
+        mInstrumentation.waitForIdleSync();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
+    }
+    /**
+     * This checks that trying to show and hide the navigation bar has no effect
+     * when the IME does not draw the IME navigation bar.
+     *
+     * Note: The IME navigation bar is *never* visible in 3 button navigation mode.
+     */
+    @Test
+    public void testShowHideImeNavigationBar_doesNotDrawImeNavBar() throws Exception {
+        boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+                .hasNavigationBar(mInputMethodService.getDisplayId());
+        assumeTrue("Must have a navigation bar", hasNavigationBar);
+
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Show IME
+        verifyInputViewStatusOnMainSync(() -> {
+            mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+                    0 /* navButtonFlags */);
+            assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue();
+        },
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
+
+        // Try to hide IME nav bar
+        mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow()
+                .getInsetsController().hide(captionBar()));
+        mInstrumentation.waitForIdleSync();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
+
+        // Try to show IME nav bar
+        mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow()
+                .getInsetsController().show(captionBar()));
+        mInstrumentation.waitForIdleSync();
+        assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
+    }
+
     private void verifyInputViewStatus(
             Runnable runnable, boolean expected, boolean inputViewStarted)
             throws InterruptedException {
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 8d76fdd..3011fa1 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -24,6 +24,8 @@
 import android.os.Binder
 import android.os.UserHandle
 import android.util.ArrayMap
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.internal.pm.pkg.component.ParsedActivity
 import com.android.server.pm.AppsFilterImpl
 import com.android.server.pm.PackageManagerService
@@ -36,9 +38,7 @@
 import com.android.server.pm.SharedLibrariesImpl
 import com.android.server.pm.UserManagerInternal
 import com.android.server.pm.UserManagerService
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.parsing.pkg.PackageImpl
-import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.snapshot.PackageDataSnapshot
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 25146a8..3461bb6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -49,11 +49,12 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.om.OverlayReferenceMapper;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
 import com.android.server.pm.pkg.component.ParsedComponentImpl;
@@ -62,7 +63,6 @@
 import com.android.server.pm.pkg.component.ParsedPermissionImpl;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.utils.WatchableTester;
 
 import org.junit.Before;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2810145..a0dc2b6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -63,10 +63,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.LocalServices;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.ArchiveState;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index 7552800..9c48af8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -72,7 +72,7 @@
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.internal.content.InstallLocationUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.test.service.server.R;
 
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 7c28e13..ea88ec2 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,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -68,6 +69,7 @@
 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.ParsingPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -75,7 +77,6 @@
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.CompatibilityPermissionInfo;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -88,7 +89,6 @@
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.junit.Before;
 import org.junit.Rule;
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 38d01d0..8a74e24 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,9 +21,9 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import junit.framework.Assert;
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
index 1c3673e..2a8e5b1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.UserHandle;
 
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 class ScanRequestBuilder {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index e2939c1..decb44c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -50,13 +50,13 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 
 import org.hamcrest.BaseMatcher;
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 7123c20..b102ab4 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,12 +38,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.component.ParsedActivityUtils;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index 3b926c2..67b91d2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -18,12 +18,11 @@
 
 import android.annotation.RawRes
 import android.content.Context
-import com.android.server.pm.pkg.parsing.ParsingPackage
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import android.content.pm.parsing.result.ParseResult
 import android.platform.test.annotations.Presubmit
 import androidx.test.InstrumentationRegistry
-import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.internal.pm.parsing.pkg.ParsedPackage
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.test.service.server.R
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
index f376e73..6cd7123 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
@@ -24,8 +24,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
index 9248da6..27fd781 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
index 23a2c20..b13d6de 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
@@ -23,8 +23,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
index 2060caa..fa69f84 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
@@ -24,9 +24,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
index b3ad861..856013a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
@@ -22,9 +22,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Before;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
index 558c0e8..ae5ea21 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
index 7a2ac75..e126ffc 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
@@ -23,8 +23,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index c4b8e6f..d0b0cf8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -28,11 +28,11 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.junit.Assume;
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
index 33fc261..d60c457 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import java.util.function.Supplier;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
index 8918233..c141c03 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
@@ -23,9 +23,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
index 3e9ec0e..a58604b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
@@ -23,9 +23,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryOrgApacheHttpLegacyLibrary;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index edab1d6..170faf6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -270,7 +270,8 @@
         AndroidPackage::getMinAspectRatio,
         AndroidPackage::hasPreserveLegacyExternalStorage,
         AndroidPackage::hasRequestForegroundServiceExemption,
-        AndroidPackage::hasRequestRawExternalStorageAccess
+        AndroidPackage::hasRequestRawExternalStorageAccess,
+        AndroidPackage::isUpdatableSystem
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index 766ab94..9341e9d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -21,9 +21,9 @@
 import android.os.Build
 import android.os.PatternMatcher
 import android.util.ArraySet
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.SystemConfig
 import com.android.server.compat.PlatformCompat
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.component.ParsedActivityImpl
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 9fbf86e..a737b90 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -28,9 +28,8 @@
 import android.util.IndentingPrintWriter
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.Computer
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
-import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
 import com.android.server.pm.pkg.component.ParsedActivityImpl
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 47d9196..f38df22 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -29,7 +29,7 @@
 import android.os.Process
 import android.util.ArraySet
 import android.util.SparseArray
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
 import com.android.server.pm.pkg.component.ParsedActivityImpl
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 98d7801..874e0d2 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -36,9 +36,8 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import android.util.Xml
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.Computer
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
-import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
 import com.android.server.pm.pkg.component.ParsedActivityImpl
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 4a211df..3207e6c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -24,7 +24,7 @@
 import android.os.Process
 import android.util.ArraySet
 import android.util.SparseArray
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
 import com.android.server.pm.pkg.component.ParsedActivityImpl
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index d54d608..a90b7d5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -26,8 +26,7 @@
 import android.os.Process
 import android.util.ArraySet
 import android.util.SparseArray
-import com.android.server.pm.parsing.pkg.AndroidPackageInternal
-import com.android.server.pm.pkg.AndroidPackage
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
 import com.android.server.pm.pkg.component.ParsedActivityImpl
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 97e5826..a2e80f0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -104,7 +104,7 @@
         468.5f,
     };
 
-    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
+    private static final int[] DISPLAY_LEVELS_INT = {
         9,
         30,
         45,
@@ -118,6 +118,20 @@
         255
     };
 
+    private static final float[] DISPLAY_LEVELS = {
+        0.03f,
+        0.11f,
+        0.17f,
+        0.24f,
+        0.3f,
+        0.37f,
+        0.46f,
+        0.57f,
+        0.7f,
+        0.87f,
+        1
+    };
+
     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
     private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
     private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
@@ -155,23 +169,23 @@
     DisplayWhiteBalanceController mMockDwbc;
 
     @Test
-    public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+    public void testSimpleStrategyMappingAtControlPoints_IntConfig() {
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
                     PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]);
+                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]);
             assertEquals(expectedLevel,
                     simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
         }
     }
 
     @Test
-    public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+    public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() {
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
@@ -179,14 +193,42 @@
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
             assertTrue("Desired brightness should be between adjacent control points.",
-                    backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
-                            && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
+                    backlight > DISPLAY_LEVELS_INT[i - 1]
+                            && backlight < DISPLAY_LEVELS_INT[i]);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingAtControlPoints_FloatConfig() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
+                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        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= */ 0.0001f);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
+                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        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;
+            final float brightness = simple.getBrightness(lux);
+            assertTrue("Desired brightness should be between adjacent control points.",
+                    brightness > DISPLAY_LEVELS[i - 1] && brightness < DISPLAY_LEVELS[i]);
         }
     }
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
@@ -201,14 +243,14 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
         strategy.setBrightnessConfiguration(null);
-        final int n = DISPLAY_LEVELS_BACKLIGHT.length;
+        final int n = DISPLAY_LEVELS_INT.length;
         final float expectedBrightness =
-                (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
+                (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON;
         assertEquals(expectedBrightness,
                 strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
     }
@@ -322,7 +364,7 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
@@ -363,13 +405,13 @@
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(strategy);
 
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        res = createResources(DISPLAY_LEVELS_INT);
         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(strategy);
 
         // Extra backlight level
         final int[] backlight = Arrays.copyOf(
-                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
+                DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
         res = createResources(backlight);
         ddc = createDdc(DISPLAY_RANGE_NITS,
@@ -410,7 +452,7 @@
                 LUX_LEVELS, DISPLAY_LEVELS_NITS);
         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
         ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        res = createResources(DISPLAY_LEVELS_INT);
         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
     }
 
@@ -546,16 +588,24 @@
         when(mockDdc.getBrightness()).thenReturn(backlightArray);
         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
         return mockDdc;
     }
 
     private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
             float[] luxLevelsFloat, float[] brightnessLevelsNits) {
+        return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits,
+                EMPTY_FLOAT_ARRAY);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
+            float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) {
         DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
         when(mockDdc.getNits()).thenReturn(nitsArray);
         when(mockDdc.getBrightness()).thenReturn(backlightArray);
         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
         return mockDdc;
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 2fd6e5f..06f1b27 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -27,24 +28,29 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.view.Display;
+import android.view.DisplayAdjustments;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.testutils.OffsettableClock;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,11 +60,12 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
 public class BrightnessSynchronizerTest {
     private static final float EPSILON = 0.00001f;
     private static final Uri BRIGHTNESS_URI =
             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
+    private static final float BRIGHTNESS_MAX = 0.6f;
 
     private Context mContext;
     private MockContentResolver mContentResolverSpy;
@@ -66,15 +73,29 @@
     private DisplayListener mDisplayListener;
     private ContentObserver mContentObserver;
     private TestLooper mTestLooper;
+    private BrightnessSynchronizer mSynchronizer;
 
     @Mock private DisplayManager mDisplayManagerMock;
     @Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
     @Captor private ArgumentCaptor<ContentObserver> mContentObserverCaptor;
 
+    // Feature flag that will eventually be removed
+    @TestParameter private boolean mIntRangeUserPerceptionEnabled;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+
+        Display display = mock(Display.class);
+        when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
+        BrightnessInfo info = new BrightnessInfo(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                PowerManager.BRIGHTNESS_MIN, BRIGHTNESS_MAX,
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, BRIGHTNESS_MAX,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        when(display.getBrightnessInfo()).thenReturn(info);
+
+        mContext = spy(new ContextWrapper(
+                ApplicationProvider.getApplicationContext().createDisplayContext(display)));
         mContentResolverSpy = spy(new MockContentResolver(mContext));
         mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
@@ -128,13 +149,12 @@
     @Test
     public void testSetSameIntValue_nothingUpdated() {
         putFloatSetting(0.5f);
-        putIntSetting(128);
         start();
 
-        putIntSetting(128);
+        putIntSetting(fToI(0.5f));
         advanceTime(10);
         verify(mDisplayManagerMock, times(0)).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
+                eq(Display.DEFAULT_DISPLAY), eq(0.5f));
     }
 
     @Test
@@ -154,14 +174,13 @@
         // Verify that this update did not get sent to float, because synchronizer
         // is still waiting for confirmation of its first value.
         verify(mDisplayManagerMock, times(0)).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+                Display.DEFAULT_DISPLAY, iToF(20));
 
         // Send the confirmation of the initial change. This should trigger the new value to
         // finally be processed and we can verify that the new value (20) is sent.
         putIntSetting(fToI(0.4f));
         advanceTime(10);
-        verify(mDisplayManagerMock).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
 
     }
 
@@ -183,8 +202,7 @@
         advanceTime(200);
 
         // Verify that the new value gets sent because the timeout expired.
-        verify(mDisplayManagerMock).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
 
         // Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
         // new event because the timeout had already expired
@@ -196,14 +214,14 @@
 
         // Verify we sent what would have been the confirmation as a new event to displaymanager.
         // We do both fToI and iToF because the conversions are not symmetric.
-        verify(mDisplayManagerMock).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
+        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY,
+                iToF(fToI(0.4f)));
     }
 
-    private BrightnessSynchronizer start() {
-        BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
-                mClock::now);
-        bs.startSynchronizing();
+    private void start() {
+        mSynchronizer = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(), mClock::now,
+                mIntRangeUserPerceptionEnabled);
+        mSynchronizer.startSynchronizing();
         verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
                 isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
         mDisplayListener = mDisplayListenerCaptor.getValue();
@@ -211,7 +229,6 @@
         verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
                 mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
         mContentObserver = mContentObserverCaptor.getValue();
-        return bs;
     }
 
     private int getIntSetting() throws Exception {
@@ -241,11 +258,19 @@
     }
 
     private int fToI(float brightness) {
-        return BrightnessSynchronizer.brightnessFloatToInt(brightness);
+        if (mIntRangeUserPerceptionEnabled) {
+            return BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness);
+        } else {
+            return BrightnessSynchronizer.brightnessFloatToInt(brightness);
+        }
     }
 
     private float iToF(int brightness) {
-        return BrightnessSynchronizer.brightnessIntToFloat(brightness);
+        if (mIntRangeUserPerceptionEnabled) {
+            return BrightnessSynchronizer.brightnessIntSettingToFloat(mContext, brightness);
+        } else {
+            return BrightnessSynchronizer.brightnessIntToFloat(brightness);
+        }
     }
 
     private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index a400f12..eb6e8b4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -44,6 +44,7 @@
         float brightness = 0.3f;
         float sdrBrightness = 0.2f;
         boolean shouldUseAutoBrightness = true;
+        boolean shouldUpdateScreenBrightnessSetting = true;
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
         brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
@@ -52,12 +53,15 @@
                 .setSdrBrightness(sdrBrightness)
                 .setBrightnessReason(brightnessReason)
                 .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+                .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting)
                 .build();
 
         assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
         assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
         assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
         assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+        assertEquals(shouldUpdateScreenBrightnessSetting,
+                displayBrightnessState.shouldUpdateScreenBrightnessSetting());
         assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
     }
 
@@ -71,6 +75,7 @@
                 .setBrightness(0.26f)
                 .setSdrBrightness(0.23f)
                 .setShouldUseAutoBrightness(false)
+                .setShouldUpdateScreenBrightnessSetting(true)
                 .build();
         DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
         assertEquals(state1, state2);
@@ -92,7 +97,9 @@
                 .append("\n    maxBrightness:")
                 .append(displayBrightnessState.getMaxBrightness())
                 .append("\n    customAnimationRate:")
-                .append(displayBrightnessState.getCustomAnimationRate());
+                .append(displayBrightnessState.getCustomAnimationRate())
+                .append("\n    shouldUpdateScreenBrightnessSetting:")
+                .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting());
         return sb.toString();
     }
 }
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 0bcbeb9..31d7e88 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -47,8 +47,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,10 +94,14 @@
     @Mock
     private Resources mResources;
 
+    @Mock
+    private DisplayManagerFlags mFlags;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
         mockDeviceConfigs();
     }
 
@@ -110,10 +116,6 @@
         assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.32f, 75.43f}, ZERO_DELTA);
 
         // Test thresholds
         assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
@@ -606,7 +608,7 @@
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
                 float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+                float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
 
         // Test thresholds
         assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -671,6 +673,9 @@
 
         assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
         assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+
+        assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+                mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
     }
 
     @Test
@@ -716,6 +721,38 @@
         assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), 0.04f, ZERO_DELTA);
     }
 
+    @Test
+    public void testBrightnessCapForWearBedtimeMode() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+        assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
+    }
+
+    @Test
+    public void testAutoBrightnessBrighteningLevels() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+
+        assertArrayEquals(new float[]{0.0f, 80},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.2f, 0.3f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+    }
+
+    @Test
+    public void testAutoBrightnessBrighteningLevels_FeatureFlagOff() throws IOException {
+        when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+
+        assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels());
+        assertArrayEquals(new float[]{0, 110, 500},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+        assertArrayEquals(new float[]{2, 200, 600},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
+    }
+
     private String getValidLuxThrottling() {
         return "<luxThrottling>\n"
                 + "    <brightnessLimitMap>\n"
@@ -1037,8 +1074,8 @@
                 +   "<screenBrightnessRampDecreaseMaxIdleMillis>"
                 +       "5000"
                 +   "</screenBrightnessRampDecreaseMaxIdleMillis>\n";
-
     }
+
     private String getContent() {
         return getContent(getValidLuxThrottling(), getValidProxSensor(),
                 /* includeIdleMode= */ true);
@@ -1089,16 +1126,18 @@
                 +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
                 +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
                 + (includeIdleMode ? getRampSpeedsIdle() : "")
-                +       "<displayBrightnessMapping>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>50</lux>\n"
-                +                "<nits>45.32</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>80</lux>\n"
-                +                "<nits>75.43</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +       "</displayBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.2</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>80</first>\n"
+                +                   "<second>0.3</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +  getPowerThrottlingConfig()
                 +   "<highBrightnessMode enabled=\"true\">\n"
@@ -1355,6 +1394,9 @@
                 +       "<majorVersion>2</majorVersion>\n"
                 +       "<minorVersion>0</minorVersion>\n"
                 +   "</usiVersion>\n"
+                +   "<screenBrightnessCapForWearBedtimeMode>"
+                +       "0.1"
+                +   "</screenBrightnessCapForWearBedtimeMode>"
                 + "</displayConfiguration>\n";
     }
 
@@ -1372,7 +1414,7 @@
     private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
         Path tempFile = Files.createTempFile("display_config", ".tmp");
         Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
-        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext, mFlags);
         mDisplayDeviceConfig.initFromFile(tempFile.toFile());
     }
 
@@ -1381,25 +1423,15 @@
         when(mResources.obtainTypedArray(
                 com.android.internal.R.array.config_screenBrightnessNits))
                 .thenReturn(screenBrightnessNits);
-        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
-                float[]{0.0f, 120.0f, 255.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessBacklight))
-                .thenReturn(screenBrightnessBacklight);
         when(mResources.getIntArray(com.android.internal.R.array
                 .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
 
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
-
         TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
                 float[]{2.0f, 200.0f, 600.0f});
         when(mResources.obtainTypedArray(
                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
                 .thenReturn(screenBrightnessLevelNits);
-        int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
+        int[] screenBrightnessLevelLux = new int[]{110, 500};
         when(mResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(screenBrightnessLevelLux);
@@ -1457,7 +1489,12 @@
                 R.integer.config_autoBrightnessDarkeningLightDebounce))
                 .thenReturn(4000);
 
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+        when(mResources.getInteger(
+                R.integer.config_screenBrightnessCapForWearBedtimeMode))
+                .thenReturn(35);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ true,
+                mFlags);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index 4fd8f26..dc6abf1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -57,6 +57,9 @@
     @Mock
     private SurfaceControl.Transaction mMockTransaction;
 
+    @Mock
+    private DisplayAdapter mMockDisplayAdapter;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -67,34 +70,39 @@
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation0() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_0, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation180() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_180, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation270() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_270, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
     }
@@ -102,8 +110,9 @@
     private static class FakeDisplayDevice extends DisplayDevice {
         private final DisplayDeviceInfo mDisplayDeviceInfo;
 
-        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo) {
-            super(null, null, "", InstrumentationRegistry.getInstrumentation().getContext());
+        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+            super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
+                    InstrumentationRegistry.getInstrumentation().getContext());
             mDisplayDeviceInfo = displayDeviceInfo;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 0bf4654..02e3ef4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -77,6 +77,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -103,6 +104,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.ContentRecordingSession;
 import android.view.Display;
+import android.view.DisplayAdjustments;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
 import android.view.DisplayInfo;
@@ -110,7 +112,6 @@
 import android.view.SurfaceControl;
 import android.window.DisplayWindowPolicyController;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
@@ -211,7 +212,8 @@
             new DisplayManagerService.Injector() {
                 @Override
                 VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                        Context context, Handler handler, DisplayAdapter.Listener listener) {
+                        Context context, Handler handler, DisplayAdapter.Listener listener,
+                        DisplayManagerFlags flags) {
                     return mMockVirtualDisplayAdapter;
                 }
 
@@ -250,7 +252,8 @@
 
         @Override
         VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+                Handler handler, DisplayAdapter.Listener displayAdapterListener,
+                DisplayManagerFlags flags) {
             return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
                     new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
                         @Override
@@ -262,7 +265,7 @@
                         @Override
                         public void destroyDisplay(IBinder displayToken) {
                         }
-                    });
+                    }, flags);
         }
 
         @Override
@@ -328,6 +331,7 @@
     @Mock SensorManager mSensorManager;
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
     @Mock PackageManagerInternal mMockPackageManagerInternal;
+    @Mock DisplayAdapter mMockDisplayAdapter;
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
     @Mock DisplayManagerFlags mMockFlags;
@@ -358,7 +362,11 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
         // TODO: b/287945043
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        Display display = mock(Display.class);
+        when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
+        when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
+        mContext = spy(new ContextWrapper(
+                ApplicationProvider.getApplicationContext().createDisplayContext(display)));
         mResources = Mockito.spy(mContext.getResources());
         manageDisplaysPermission(/* granted= */ false);
         when(mContext.getResources()).thenReturn(mResources);
@@ -783,7 +791,8 @@
                 new DisplayManagerService.Injector() {
                     @Override
                     VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                            Context context, Handler handler, DisplayAdapter.Listener listener) {
+                            Context context, Handler handler, DisplayAdapter.Listener listener,
+                            DisplayManagerFlags flags) {
                         return null;  // return null for the adapter.  This should cause a failure.
                     }
 
@@ -2163,7 +2172,6 @@
 
     @Test
     public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
-        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
 
         // get the first two internal displays
@@ -2754,8 +2762,7 @@
         DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class);
         localService.registerDisplayOffloader(displayId, mockDisplayOffloader);
 
-        assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo(
-                mockDisplayOffloader);
+        assertThat(display.getDisplayOffloadSessionLocked()).isNotNull();
     }
 
     @Test
@@ -3191,7 +3198,7 @@
         private DisplayDeviceInfo mDisplayDeviceInfo;
 
         FakeDisplayDevice() {
-            super(null, null, "", mContext);
+            super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext);
         }
 
         public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
new file mode 100644
index 0000000..dea838d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class DisplayOffloadSessionImplTest {
+
+    @Mock
+    private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
+
+    @Mock
+    private DisplayPowerControllerInterface mDisplayPowerController;
+
+    private DisplayOffloadSessionImpl mSession;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mDisplayOffloader.startOffload()).thenReturn(true);
+        mSession = new DisplayOffloadSessionImpl(mDisplayOffloader, mDisplayPowerController);
+    }
+
+    @Test
+    public void testStartOffload() {
+        mSession.startOffload();
+        assertTrue(mSession.isActive());
+
+        // An active session shouldn't be started again
+        mSession.startOffload();
+        verify(mDisplayOffloader, times(1)).startOffload();
+    }
+
+    @Test
+    public void testStopOffload() {
+        mSession.startOffload();
+        mSession.stopOffload();
+
+        assertFalse(mSession.isActive());
+        verify(mDisplayPowerController).setBrightnessFromOffload(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        // An inactive session shouldn't be stopped again
+        mSession.stopOffload();
+        verify(mDisplayOffloader, times(1)).stopOffload();
+    }
+
+    @Test
+    public void testUpdateBrightness_sessionInactive() {
+        mSession.updateBrightness(0.3f);
+        verify(mDisplayPowerController, never()).setBrightnessFromOffload(anyFloat());
+    }
+
+    @Test
+    public void testUpdateBrightness_sessionActive() {
+        float brightness = 0.3f;
+
+        mSession.startOffload();
+        mSession.updateBrightness(brightness);
+
+        verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
+    }
+}
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 57f392a..693cafe 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -127,8 +127,6 @@
     private Handler mHandler;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
-    private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
 
     @Mock
     private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -148,6 +146,8 @@
     private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
     @Mock
     private DisplayManagerFlags mDisplayManagerFlagsMock;
+    @Mock
+    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
@@ -1160,19 +1160,19 @@
                 any(AutomaticBrightnessController.Callbacks.class),
                 any(Looper.class),
                 eq(mSensorManagerMock),
-                any(),
+                /* lightSensor= */ any(),
                 eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
+                /* lightSensorWarmUpTime= */ anyInt(),
+                /* brightnessMin= */ anyFloat(),
+                /* brightnessMax= */ anyFloat(),
+                /* dozeScaleFactor */ anyFloat(),
+                /* lightSensorRate= */ anyInt(),
+                /* initialLightSensorRate= */ anyInt(),
+                /* brighteningLightDebounceConfig */ anyLong(),
+                /* darkeningLightDebounceConfig */ anyLong(),
+                /* brighteningLightDebounceConfigIdle= */ anyLong(),
+                /* darkeningLightDebounceConfigIdle= */ anyLong(),
+                /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
@@ -1180,9 +1180,9 @@
                 eq(mContext),
                 any(BrightnessRangeController.class),
                 any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
+                /* idleModeBrightnessMapper= */ isNull(),
+                /* ambientLightHorizonShort= */ anyInt(),
+                /* ambientLightHorizonLong= */ anyInt(),
                 eq(lux),
                 eq(brightness)
         );
@@ -1294,9 +1294,8 @@
     public void testRampRateForHdrContent_HdrClamperOn() {
         float clampedBrightness = 0.6f;
         float transitionRate = 1.5f;
-        DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
-        when(flags.isHdrClamperEnabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+        when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
@@ -1392,9 +1391,8 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
-        DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
-        when(flags.isAdaptiveTone1Enabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+        when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
 
         // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1447,9 +1445,8 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeIdle_DifferentValues() {
-        DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
-        when(flags.isAdaptiveTone1Enabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags);
+        when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
 
         // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1481,8 +1478,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with DOZE.
@@ -1509,8 +1504,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with DOZE.
@@ -1536,8 +1529,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with OFF.
@@ -1553,26 +1544,29 @@
         verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
     }
 
-    private void initDisplayOffloadSession() {
-        mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
-            @Override
-            public boolean startOffload() {
-                return true;
-            }
+    @Test
+    public void testBrightnessFromOffload() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        float brightness = 0.34f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
-            @Override
-            public void stopOffload() {}
-        });
+        mHolder.dpc.setBrightnessFromOffload(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
 
-        mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
-            @Override
-            public void setDozeStateOverride(int displayState) {}
-
-            @Override
-            public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
-                return mDisplayOffloader;
-            }
-        };
+        // One triggered by handleBrightnessModeChange, another triggered by
+        // setBrightnessFromOffload
+        verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
     }
 
     /**
@@ -1659,12 +1653,6 @@
 
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId, boolean isEnabled) {
-        return createDisplayPowerController(displayId, uniqueId, isEnabled,
-                mock(DisplayManagerFlags.class));
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled, DisplayManagerFlags flags) {
         final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
         final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
         final AutomaticBrightnessController automaticBrightnessController =
@@ -1690,7 +1678,7 @@
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
                 hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
-                clamperController, flags));
+                clamperController, mDisplayManagerFlagsMock));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1705,7 +1693,7 @@
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {
         },
-                hbmMetadata, /* bootCompleted= */ false, flags);
+                hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
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 9617bd0..b227993 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -125,8 +125,6 @@
     private Handler mHandler;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
-    private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
 
     @Mock
     private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -146,6 +144,8 @@
     private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
     @Mock
     private DisplayManagerFlags mDisplayManagerFlagsMock;
+    @Mock
+    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -1094,19 +1094,19 @@
                 any(AutomaticBrightnessController.Callbacks.class),
                 any(Looper.class),
                 eq(mSensorManagerMock),
-                any(),
+                /* lightSensor= */ any(),
                 eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
+                /* lightSensorWarmUpTime= */ anyInt(),
+                /* brightnessMin= */ anyFloat(),
+                /* brightnessMax= */ anyFloat(),
+                /* dozeScaleFactor */ anyFloat(),
+                /* lightSensorRate= */ anyInt(),
+                /* initialLightSensorRate= */ anyInt(),
+                /* brighteningLightDebounceConfig */ anyLong(),
+                /* darkeningLightDebounceConfig */ anyLong(),
+                /* brighteningLightDebounceConfigIdle= */ anyLong(),
+                /* darkeningLightDebounceConfigIdle= */ anyLong(),
+                /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
@@ -1114,9 +1114,9 @@
                 eq(mContext),
                 any(BrightnessRangeController.class),
                 any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
+                /* idleModeBrightnessMapper= */ isNull(),
+                /* ambientLightHorizonShort= */ anyInt(),
+                /* ambientLightHorizonLong= */ anyInt(),
                 eq(lux),
                 eq(brightness)
         );
@@ -1386,8 +1386,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with DOZE.
@@ -1414,8 +1412,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with DOZE.
@@ -1441,8 +1437,6 @@
             when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
             return null;
         }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        // init displayoffload session and support offloading.
-        initDisplayOffloadSession();
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
 
         // start with OFF.
@@ -1458,28 +1452,6 @@
         verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
     }
 
-    private void initDisplayOffloadSession() {
-        mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() {
-            @Override
-            public boolean startOffload() {
-                return true;
-            }
-
-            @Override
-            public void stopOffload() {}
-        });
-
-        mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() {
-            @Override
-            public void setDozeStateOverride(int displayState) {}
-
-            @Override
-            public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() {
-                return mDisplayOffloader;
-            }
-        };
-    }
-
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index a77a958..f36854b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -25,6 +25,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -40,12 +41,12 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
@@ -118,10 +119,12 @@
     private DisplayNotificationManager mMockedDisplayNotificationManager;
     @Mock
     private DisplayManagerFlags mFlags;
+    @Mock
+    private DisplayPowerControllerInterface mMockedDisplayPowerController;
 
     private Handler mHandler;
 
-    private DisplayOffloadSession mDisplayOffloadSession;
+    private DisplayOffloadSessionImpl mDisplayOffloadSession;
 
     private DisplayOffloader mDisplayOffloader;
 
@@ -1195,6 +1198,7 @@
         }
 
         verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
+        assertTrue(mDisplayOffloadSession.isActive());
     }
 
     @Test
@@ -1217,6 +1221,9 @@
         changeStateToDozeRunnable.run();
 
         verify(mDisplayOffloader).stopOffload();
+        assertFalse(mDisplayOffloadSession.isActive());
+        verify(mMockedDisplayPowerController).setBrightnessFromOffload(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
     }
 
     private void initDisplayOffloadSession() {
@@ -1230,15 +1237,8 @@
             public void stopOffload() {}
         });
 
-        mDisplayOffloadSession = new DisplayOffloadSession() {
-            @Override
-            public void setDozeStateOverride(int displayState) {}
-
-            @Override
-            public DisplayOffloader getDisplayOffloader() {
-                return mDisplayOffloader;
-            }
-        };
+        mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
+                mMockedDisplayPowerController);
     }
 
     private void setupCutoutAndRoundedCorners() {
@@ -1455,8 +1455,8 @@
         // mMockContext and values will be loaded from mMockResources.
         @Override
         public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
-                long physicalDisplayId, boolean isFirstDisplay) {
-            return DisplayDeviceConfig.create(context, isFirstDisplay);
+                long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
+            return DisplayDeviceConfig.create(context, isFirstDisplay, flags);
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 43d2b8f..28ec896 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -119,8 +119,8 @@
     @Mock IThermalService mIThermalServiceMock;
     @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
             new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
-    @Mock
-    DisplayManagerFlags mFlagsMock;
+    @Mock DisplayManagerFlags mFlagsMock;
+    @Mock DisplayAdapter mDisplayAdapterMock;
 
     @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
     @Captor ArgumentCaptor<Integer> mDisplayEventCaptor;
@@ -1075,7 +1075,8 @@
         private int mState;
 
         TestDisplayDevice() {
-            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
+            super(mDisplayAdapterMock, /* displayToken= */ null,
+                    "test_display_" + sUniqueTestDisplayId++, mContextMock);
             mInfo = new DisplayDeviceInfo();
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 9f91916..5676a38 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -32,9 +32,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -51,8 +54,12 @@
     private TestInjector mInjector;
     private TestLooper mTestLooper;
 
+    @Mock
+    private DisplayAdapter mDisplayAdapter;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mInjector = new TestInjector();
         mTestLooper = new TestLooper();
         Handler handler = new Handler(mTestLooper.getLooper());
@@ -62,8 +69,8 @@
     @Test
     public void testLoadBrightness() {
         final String uniqueDisplayId = "test:123";
-        final DisplayDevice testDisplayDevice = new DisplayDevice(
-                null, null, uniqueDisplayId, null) {
+        final DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -100,7 +107,8 @@
     public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
         final String uniqueDisplayId = "test:123";
         final DisplayDevice testDisplayDevice =
-                new DisplayDevice(null, null, uniqueDisplayId, null) {
+                new DisplayDevice(mDisplayAdapter, /* displayToken= */ null, uniqueDisplayId,
+                        /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -273,7 +281,8 @@
         assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
                 userSerial));
 
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -319,7 +328,8 @@
         assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
                 userSerial));
 
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return false;
@@ -386,7 +396,8 @@
     @Test
     public void testStoreAndRestoreResolution() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -422,7 +433,8 @@
     @Test
     public void testStoreAndRestoreRefreshRate() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -455,7 +467,8 @@
     @Test
     public void testBrightnessInitialisesWithInvalidFloat() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 73e7ba0..c01b15c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -28,6 +28,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
@@ -59,13 +60,17 @@
 
     private VirtualDisplayAdapter mVirtualDisplayAdapter;
 
+    @Mock
+    private DisplayManagerFlags mFeatureFlags;
+
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mHandler = new TestHandler(null);
         mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(),
-                mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory);
+                mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory,
+                mFeatureFlags);
 
         when(mMockCallback.asBinder()).thenReturn(mMockBinder);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c4f4838..52fa91f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -42,7 +42,9 @@
 import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
 import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,6 +68,8 @@
     private BrightnessSetting mBrightnessSetting;
     @Mock
     private Runnable mOnBrightnessChangeRunnable;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlags;
 
     @Mock
     private HandlerExecutor mBrightnessChangeExecutor;
@@ -74,7 +78,7 @@
             DisplayBrightnessController.Injector() {
         @Override
         DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
-                Context context, int displayId) {
+                Context context, int displayId, DisplayManagerFlags flags) {
             return mDisplayBrightnessStrategySelector;
         }
     };
@@ -92,7 +96,7 @@
                 .thenReturn(true);
         mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
                 DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
-                mBrightnessChangeExecutor);
+                mBrightnessChangeExecutor, mDisplayManagerFlags);
     }
 
     @Test
@@ -350,7 +354,7 @@
         int nonDefaultDisplayId = 1;
         mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
                 nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
-                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
+                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor, mDisplayManagerFlags);
         brightness = 0.5f;
         when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
         mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -384,4 +388,14 @@
         verify(mBrightnessSetting).setBrightness(brightnessValue2);
         verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
     }
+
+    @Test
+    public void setBrightnessFromOffload() {
+        float brightness = 0.4f;
+        OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+                offloadBrightnessStrategy);
+        mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 8497dab..37958fa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display.brightness;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -35,13 +36,16 @@
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
 import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
 import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,10 +75,64 @@
     @Mock
     private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
     @Mock
+    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+    @Mock
+    private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+    @Mock
     private Resources mResources;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlags;
 
     private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
     private Context mContext;
+    private DisplayBrightnessStrategySelector.Injector mInjector =
+            new DisplayBrightnessStrategySelector.Injector() {
+                @Override
+                ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+                    return mScreenOffBrightnessModeStrategy;
+                }
+
+                @Override
+                DozeBrightnessStrategy getDozeBrightnessStrategy() {
+                    return mDozeBrightnessModeStrategy;
+                }
+
+                @Override
+                OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+                    return mOverrideBrightnessStrategy;
+                }
+
+                @Override
+                TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+                    return mTemporaryBrightnessStrategy;
+                }
+
+                @Override
+                BoostBrightnessStrategy getBoostBrightnessStrategy() {
+                    return mBoostBrightnessStrategy;
+                }
+
+                @Override
+                FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
+                    return mFollowerBrightnessStrategy;
+                }
+
+                @Override
+                InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+                    return mInvalidBrightnessStrategy;
+                }
+
+                @Override
+                AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context,
+                        int displayId) {
+                    return mAutomaticBrightnessStrategy;
+                }
+
+                @Override
+                OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
+                    return mOffloadBrightnessStrategy;
+                }
+            };
 
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@@ -87,45 +145,8 @@
         when(mContext.getContentResolver()).thenReturn(contentResolver);
         when(mContext.getResources()).thenReturn(mResources);
         when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
-        DisplayBrightnessStrategySelector.Injector injector =
-                new DisplayBrightnessStrategySelector.Injector() {
-                    @Override
-                    ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
-                        return mScreenOffBrightnessModeStrategy;
-                    }
-
-                    @Override
-                    DozeBrightnessStrategy getDozeBrightnessStrategy() {
-                        return mDozeBrightnessModeStrategy;
-                    }
-
-                    @Override
-                    OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
-                        return mOverrideBrightnessStrategy;
-                    }
-
-                    @Override
-                    TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
-                        return mTemporaryBrightnessStrategy;
-                    }
-
-                    @Override
-                    BoostBrightnessStrategy getBoostBrightnessStrategy() {
-                        return mBoostBrightnessStrategy;
-                    }
-
-                    @Override
-                    FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
-                        return mFollowerBrightnessStrategy;
-                    }
-
-                    @Override
-                    InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
-                        return mInvalidBrightnessStrategy;
-                    }
-                };
         mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
-                injector, DISPLAY_ID);
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
 
     }
 
@@ -188,6 +209,7 @@
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
                 Display.STATE_ON), mInvalidBrightnessStrategy);
     }
@@ -200,4 +222,35 @@
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
                 Display.STATE_ON), mFollowerBrightnessStrategy);
     }
+
+    @Test
+    public void selectStrategySelectsOffloadStrategyWhenValid() {
+        when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
+        assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
+                displayPowerRequest, Display.STATE_ON));
+    }
+
+    @Test
+    public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() {
+        when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
+        assertNotEquals(mOffloadBrightnessStrategy,
+                mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                        Display.STATE_ON));
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index ff2b1f4..9aa6136 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -262,7 +262,8 @@
                 Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                Context context) {
             mCapturedChangeListener = clamperChangeListener;
             return mClampers;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
new file mode 100644
index 0000000..3458b08
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF;
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class BrightnessWearBedtimeModeClamperTest {
+
+    private static final float BRIGHTNESS_CAP = 0.3f;
+
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private final TestInjector mInjector = new TestInjector();
+    private BrightnessWearBedtimeModeClamper mClamper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext,
+                mMockClamperChangeListener, () -> BRIGHTNESS_CAP);
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testBrightnessCap() {
+        assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
+    }
+
+    @Test
+    public void testBedtimeModeOn() {
+        setBedtimeModeEnabled(true);
+        assertTrue(mClamper.isActive());
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testBedtimeModeOff() {
+        setBedtimeModeEnabled(false);
+        assertFalse(mClamper.isActive());
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(BrightnessClamper.Type.BEDTIME_MODE, mClamper.getType());
+    }
+
+    @Test
+    public void testOnDisplayChanged() {
+        float newBrightnessCap = 0.61f;
+
+        mClamper.onDisplayChanged(() -> newBrightnessCap);
+        mTestHandler.flush();
+
+        assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    private void setBedtimeModeEnabled(boolean enabled) {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+                enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF);
+        mInjector.notifyBedtimeModeChanged();
+        mTestHandler.flush();
+    }
+
+    private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector {
+
+        private ContentObserver mObserver;
+
+        @Override
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mObserver = observer;
+        }
+
+        private void notifyBedtimeModeChanged() {
+            if (mObserver != null) {
+                mObserver.dispatchChange(/* selfChange= */ false,
+                        Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE));
+            }
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index b652576..78ec2ff3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,6 +188,29 @@
     }
 
     @Test
+    public void testAutoBrightnessState_BrightnessReasonIsOffload() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_OFFLOAD;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
     public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
         mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
         int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
new file mode 100644
index 0000000..36719af
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OffloadBrightnessStrategyTest {
+
+    private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mOffloadBrightnessStrategy = new OffloadBrightnessStrategy();
+    }
+
+    @Test
+    public void testUpdateBrightnessWhenOffloadBrightnessIsSet() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float brightness = 0.2f;
+        mOffloadBrightnessStrategy.setOffloadScreenBrightness(brightness);
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(brightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(brightness)
+                        .setDisplayBrightnessStrategyName(mOffloadBrightnessStrategy.getName())
+                        .setShouldUpdateScreenBrightnessSetting(true)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mOffloadBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt
new file mode 100644
index 0000000..3f72364
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+private const val BASE_REFRESH_RATE = 60f
+private const val OTHER_BASE_REFRESH_RATE = 90f
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BaseModeRefreshRateVoteTest {
+
+    private lateinit var baseModeVote: BaseModeRefreshRateVote
+
+    @Before
+    fun setUp() {
+        baseModeVote = BaseModeRefreshRateVote(BASE_REFRESH_RATE)
+    }
+
+    @Test
+    fun `updates summary with base mode refresh rate if not set`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+
+        baseModeVote.updateSummary(summary)
+
+        assertThat(summary.appRequestBaseModeRefreshRate).isEqualTo(BASE_REFRESH_RATE)
+    }
+
+    @Test
+    fun `keeps summary base mode refresh rate if set`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.appRequestBaseModeRefreshRate = OTHER_BASE_REFRESH_RATE
+
+        baseModeVote.updateSummary(summary)
+
+        assertThat(summary.appRequestBaseModeRefreshRate).isEqualTo(OTHER_BASE_REFRESH_RATE)
+    }
+
+    @Test
+    fun `keeps summary with base mode refresh rate if vote refresh rate is negative`() {
+        val invalidBaseModeVote = BaseModeRefreshRateVote(-10f)
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+
+        invalidBaseModeVote.updateSummary(summary)
+
+        assertThat(summary.appRequestBaseModeRefreshRate).isZero()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt
new file mode 100644
index 0000000..7f8da88
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+
+
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CombinedVoteTest {
+    private lateinit var combinedVote: CombinedVote
+
+    @get:Rule
+    val mockitoRule = MockitoJUnit.rule()
+
+    private val mockVote1 = mock<Vote>()
+    private val mockVote2 = mock<Vote>()
+
+    @Before
+    fun setUp() {
+        combinedVote = CombinedVote(listOf(mockVote1, mockVote2))
+    }
+
+    @Test
+    fun `delegates update to children`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+
+        combinedVote.updateSummary(summary)
+
+        verify(mockVote1).updateSummary(summary)
+        verify(mockVote2).updateSummary(summary)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt
new file mode 100644
index 0000000..c624325
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.display.mode
+
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class DisableRefreshRateSwitchingVoteTest {
+
+    @Test
+    fun `disabled refresh rate switching is not changed`(
+            @TestParameter voteDisableSwitching: Boolean
+    ) {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.disableRefreshRateSwitching = true
+        val vote = DisableRefreshRateSwitchingVote(voteDisableSwitching)
+
+        vote.updateSummary(summary)
+
+        assertThat(summary.disableRefreshRateSwitching).isTrue()
+    }
+
+    @Test
+    fun `disables refresh rate switching if requested`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        val vote = DisableRefreshRateSwitchingVote(true)
+
+        vote.updateSummary(summary)
+
+        assertThat(summary.disableRefreshRateSwitching).isTrue()
+    }
+
+    @Test
+    fun `does not disable refresh rate switching if not requested`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        val vote = DisableRefreshRateSwitchingVote(false)
+
+        vote.updateSummary(summary)
+
+        assertThat(summary.disableRefreshRateSwitching).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 499e700..60a0c03 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -28,7 +28,6 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.display.mode.Vote.INVALID_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -1192,7 +1191,9 @@
         assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
     }
 
     @Test
@@ -1271,7 +1272,9 @@
         assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
 
         // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
         // parameter to the necessary threshold
@@ -1340,7 +1343,9 @@
         assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
     }
 
     @Test
@@ -1423,7 +1428,9 @@
         assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
 
         // Set critical and check new refresh rate
         Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
@@ -1435,7 +1442,9 @@
         assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
     }
 
     @Test
@@ -1518,7 +1527,9 @@
         assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
 
         // Set critical and check new refresh rate
         Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
@@ -1530,7 +1541,9 @@
         assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
         vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
         assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
+        assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+        disableVote = (DisableRefreshRateSwitchingVote) vote;
+        assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
     }
 
     @Test
@@ -1800,61 +1813,43 @@
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
 
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
+        BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
+        assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
 
-        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestSize.appRequestBaseModeRefreshRate).isZero();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(SizeVote.class);
+        SizeVote sizeVote = (SizeVote) vote;
+        assertThat(sizeVote.mHeight).isEqualTo(1000);
+        assertThat(sizeVote.mWidth).isEqualTo(1000);
+        assertThat(sizeVote.mMinHeight).isEqualTo(1000);
+        assertThat(sizeVote.mMinWidth).isEqualTo(1000);
 
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNull(appRequestRefreshRateRange);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNull(vote);
 
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
 
-        appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
+        baseModeVote = (BaseModeRefreshRateVote) vote;
+        assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
 
-        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(SizeVote.class);
+        sizeVote = (SizeVote) vote;
+        assertThat(sizeVote.mHeight).isEqualTo(1000);
+        assertThat(sizeVote.mWidth).isEqualTo(1000);
+        assertThat(sizeVote.mMinHeight).isEqualTo(1000);
+        assertThat(sizeVote.mMinWidth).isEqualTo(1000);
 
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNull(appRequestRefreshRateRange);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNull(vote);
     }
 
     @Test
@@ -1868,17 +1863,12 @@
         Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
         assertNull(appRequestSize);
 
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
 
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
         appRequestRefreshRate =
@@ -1888,18 +1878,12 @@
         appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
         assertNull(appRequestSize);
 
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
     }
 
     @Test
@@ -1913,18 +1897,12 @@
         Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
         assertNull(appRequestSize);
 
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isZero();
+        assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
 
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
         appRequestRefreshRate =
@@ -1934,18 +1912,12 @@
         appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
         assertNull(appRequestSize);
 
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isZero();
+        assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
     }
 
     @Test
@@ -1969,41 +1941,27 @@
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
 
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
+        BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
+        assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
 
-        Vote appRequestSize =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(SizeVote.class);
+        SizeVote sizeVote = (SizeVote) vote;
+        assertThat(sizeVote.mHeight).isEqualTo(1000);
+        assertThat(sizeVote.mWidth).isEqualTo(1000);
+        assertThat(sizeVote.mMinHeight).isEqualTo(1000);
+        assertThat(sizeVote.mMinWidth).isEqualTo(1000);
 
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(vote);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
     }
 
     @Test
@@ -3073,8 +3031,7 @@
         captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
-        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertVoteForPhysicalRefreshRate(vote, 90);
     }
 
     @Test
@@ -3107,8 +3064,7 @@
         captor.getValue().onRequestEnabled(DISPLAY_ID);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertVoteForPhysicalRefreshRate(vote, 90);
     }
 
     @Test
@@ -3180,16 +3136,21 @@
 
     private void assertVoteForPhysicalRefreshRate(Vote vote, float refreshRate) {
         assertThat(vote).isNotNull();
-        final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
-        assertThat(vote.refreshRateRanges.physical).isEqualTo(expectedRange);
+        assertThat(vote).isInstanceOf(CombinedVote.class);
+        CombinedVote combinedVote = (CombinedVote) vote;
+        RefreshRateVote.PhysicalVote physicalVote =
+                (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0);
+        assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(refreshRate);
+        assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(refreshRate);
     }
 
     private void assertVoteForRenderFrameRateRange(
             Vote vote, float frameRateLow, float frameRateHigh) {
         assertThat(vote).isNotNull();
-        final RefreshRateRange expectedRange =
-                new RefreshRateRange(frameRateLow, frameRateHigh);
-        assertThat(vote.refreshRateRanges.render).isEqualTo(expectedRange);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertThat(renderVote.mMinRefreshRate).isEqualTo(frameRateLow);
+        assertThat(renderVote.mMaxRefreshRate).isEqualTo(frameRateHigh);
     }
 
     public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt
new file mode 100644
index 0000000..547008e
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.display.mode
+
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val MIN_REFRESH_RATE = 60f
+private const val MAX_REFRESH_RATE = 90f
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class PhysicalVoteTest {
+    private lateinit var physicalVote: RefreshRateVote.PhysicalVote
+
+    @Before
+    fun setUp() {
+        physicalVote = RefreshRateVote.PhysicalVote(MIN_REFRESH_RATE, MAX_REFRESH_RATE)
+    }
+
+    @Test
+    fun `updates minPhysicalRefreshRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minPhysicalRefreshRate = 45f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.minPhysicalRefreshRate).isEqualTo(MIN_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update minPhysicalRefreshRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minPhysicalRefreshRate = 75f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.minPhysicalRefreshRate).isEqualTo(75f)
+    }
+
+    @Test
+    fun `updates maxPhysicalRefreshRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxPhysicalRefreshRate = 120f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.maxPhysicalRefreshRate).isEqualTo(MAX_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update maxPhysicalRefreshRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxPhysicalRefreshRate = 75f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.maxPhysicalRefreshRate).isEqualTo(75f)
+    }
+
+    @Test
+    fun `updates maxRenderFrameRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxRenderFrameRate = 120f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.maxRenderFrameRate).isEqualTo(MAX_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update maxRenderFrameRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxRenderFrameRate = 75f
+
+        physicalVote.updateSummary(summary)
+
+        assertThat(summary.maxRenderFrameRate).isEqualTo(75f)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt
new file mode 100644
index 0000000..868a893
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val MIN_REFRESH_RATE = 60f
+private const val MAX_REFRESH_RATE = 90f
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class RenderVoteTest {
+
+    private lateinit var renderVote: RefreshRateVote.RenderVote
+
+    @Before
+    fun setUp() {
+        renderVote = RefreshRateVote.RenderVote(MIN_REFRESH_RATE, MAX_REFRESH_RATE)
+    }
+
+    @Test
+    fun `updates minRenderFrameRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minRenderFrameRate = 45f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.minRenderFrameRate).isEqualTo(MIN_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update minRenderFrameRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minRenderFrameRate = 75f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.minRenderFrameRate).isEqualTo(75f)
+    }
+
+    @Test
+    fun `updates maxRenderFrameRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxRenderFrameRate = 120f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.maxRenderFrameRate).isEqualTo(MAX_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update maxRenderFrameRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.maxRenderFrameRate = 75f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.maxRenderFrameRate).isEqualTo(75f)
+    }
+
+    @Test
+    fun `updates minPhysicalRefreshRate if summary has less`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minPhysicalRefreshRate = 45f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.minPhysicalRefreshRate).isEqualTo(MIN_REFRESH_RATE)
+    }
+
+    @Test
+    fun `does not update minPhysicalRefreshRate if summary has more`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.minPhysicalRefreshRate = 75f
+
+        renderVote.updateSummary(summary)
+
+        assertThat(summary.minPhysicalRefreshRate).isEqualTo(75f)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt
new file mode 100644
index 0000000..1c631b0
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+private const val WIDTH = 800
+private const val HEIGHT = 1600
+private const val MIN_WIDTH = 400
+private const val MIN_HEIGHT = 1200
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SizeVoteTest {
+    private lateinit var sizeVote: SizeVote
+
+    @Before
+    fun setUp() {
+        sizeVote = SizeVote(WIDTH, HEIGHT, MIN_WIDTH, MIN_HEIGHT)
+    }
+
+    @Test
+    fun `updates size if width and height not set and display resolution voting disabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false)
+        summary.width = Vote.INVALID_SIZE
+        summary.height = Vote.INVALID_SIZE
+        summary.minWidth = 100
+        summary.minHeight = 200
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.width).isEqualTo(WIDTH)
+        assertThat(summary.height).isEqualTo(HEIGHT)
+        assertThat(summary.minWidth).isEqualTo(MIN_WIDTH)
+        assertThat(summary.minHeight).isEqualTo(MIN_HEIGHT)
+    }
+
+    @Test
+    fun `does not update size if width set and display resolution voting disabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false)
+        summary.width = 150
+        summary.height = Vote.INVALID_SIZE
+        summary.minWidth = 100
+        summary.minHeight = 200
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.width).isEqualTo(150)
+        assertThat(summary.height).isEqualTo(Vote.INVALID_SIZE)
+        assertThat(summary.minWidth).isEqualTo(100)
+        assertThat(summary.minHeight).isEqualTo(200)
+    }
+
+    @Test
+    fun `does not update size if height set and display resolution voting disabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false)
+        summary.width = Vote.INVALID_SIZE
+        summary.height = 250
+        summary.minWidth = 100
+        summary.minHeight = 200
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.width).isEqualTo(Vote.INVALID_SIZE)
+        assertThat(summary.height).isEqualTo(250)
+        assertThat(summary.minWidth).isEqualTo(100)
+        assertThat(summary.minHeight).isEqualTo(200)
+    }
+
+    @Test
+    fun `updates width if summary has more and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 850
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.width).isEqualTo(WIDTH)
+    }
+
+    @Test
+    fun `does not update width if summary has less and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 750
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.width).isEqualTo(750)
+    }
+
+    @Test
+    fun `updates height if summary has more and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.height = 1650
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.height).isEqualTo(HEIGHT)
+    }
+
+    @Test
+    fun `does not update height if summary has less and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.height = 1550
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.height).isEqualTo(1550)
+    }
+
+    @Test
+    fun `updates minWidth if summary has less and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 150
+        summary.minWidth = 350
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.minWidth).isEqualTo(MIN_WIDTH)
+    }
+
+    @Test
+    fun `does not update minWidth if summary has more and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 150
+        summary.minWidth = 450
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.minWidth).isEqualTo(450)
+    }
+
+    @Test
+    fun `updates minHeight if summary has less and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 150
+        summary.minHeight = 1150
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.minHeight).isEqualTo(MIN_HEIGHT)
+    }
+
+    @Test
+    fun `does not update minHeight if summary has more and display resolution voting enabled`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.width = 150
+        summary.minHeight = 1250
+
+        sizeVote.updateSummary(summary)
+
+        assertThat(summary.minHeight).isEqualTo(1250)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
index 9ab6ee5..f677401 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -17,6 +17,8 @@
 package com.android.server.display.mode;
 
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -102,17 +104,21 @@
         SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
 
-        Vote vote = displayVotes.get(
-                Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
-        assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
+
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE);
 
         SparseArray<Vote> otherDisplayVotes = mStorage.getVotes(DISPLAY_ID_OTHER);
         assertEquals(1, otherDisplayVotes.size());
 
         vote = otherDisplayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
-        assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        renderVote = (RefreshRateVote.RenderVote) vote;
+        assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE);
     }
 
     @Test
@@ -167,8 +173,10 @@
         SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
         Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
-        assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertEquals(90, renderVote.mMinRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(120, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE);
         assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
@@ -188,8 +196,10 @@
         SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID_ADDED);
 
         Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
-        assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
+        RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
+        assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE);
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
new file mode 100644
index 0000000..cc88003
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.server.display.mode.DisplayModeDirector.VoteSummary
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SupportedModesVoteTest {
+    private val supportedModes = listOf(
+            SupportedModesVote.SupportedMode(60f, 90f ),
+            SupportedModesVote.SupportedMode(120f, 240f )
+    )
+
+    private val otherMode = SupportedModesVote.SupportedMode(120f, 120f )
+
+    private lateinit var supportedModesVote: SupportedModesVote
+
+    @Before
+    fun setUp() {
+        supportedModesVote = SupportedModesVote(supportedModes)
+    }
+
+    @Test
+    fun `adds supported modes if supportedModes in summary is null`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+
+        supportedModesVote.updateSummary(summary)
+
+        assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes)
+    }
+
+    @Test
+    fun `does not add supported modes if summary has empty list of modes`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.supportedModes = ArrayList()
+
+        supportedModesVote.updateSummary(summary)
+
+        assertThat(summary.supportedModes).isEmpty()
+    }
+
+    @Test
+    fun `filters out modes that does not match vote`() {
+        val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true)
+        summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0]))
+
+        supportedModesVote.updateSummary(summary)
+
+        assertThat(summary.supportedModes).containsExactly(supportedModes[0])
+    }
+}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index be29163..1a3a6a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -46,6 +46,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -61,6 +62,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.Sensor;
@@ -128,6 +130,8 @@
     @Mock
     private ConnectivityManager mConnectivityManager;
     @Mock
+    private ContentResolver mContentResolver;
+    @Mock
     private IActivityManager mIActivityManager;
     @Mock
     private LocationManager mLocationManager;
@@ -332,7 +336,7 @@
                         anyString(), any(Executor.class),
                         any(DeviceConfig.OnPropertiesChangedListener.class)));
         doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock
-                -> mock(DeviceConfig.Properties.class))
+                -> new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_DEVICE_IDLE).build())
                 .when(() -> DeviceConfig.getProperties(
                         anyString(), ArgumentMatchers.<String>any()));
         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
@@ -347,6 +351,7 @@
         mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
         mAnyMotionDetector = new AnyMotionDetectorForTest();
         mInjector = new InjectorForTest(getContext());
+        doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
 
         doReturn(mWearModeManagerInternal)
                 .when(() -> LocalServices.getService(WearModeManagerInternal.class));
@@ -366,7 +371,8 @@
         mDeviceIdleController.setLightEnabledForTest(true);
 
         // Get the same Constants object that mDeviceIdleController got.
-        mConstants = mInjector.getConstants(mDeviceIdleController);
+        mConstants = mInjector.getConstants(mDeviceIdleController,
+                mInjector.getHandler(mDeviceIdleController), mContentResolver);
 
         final ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor =
                 ArgumentCaptor.forClass(TelephonyCallback.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 92d1118..4f672f8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -19,6 +19,7 @@
 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
 import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT;
 import static android.app.AppOpsManager._NUM_OP;
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -208,8 +209,8 @@
     private void assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2) {
         for (int uid : testService.getUidsWithNonDefaultModes()) {
             assertEquals(
-                    testService.getUidMode(uid, op1),
-                    testService.getUidMode(uid, op2)
+                    testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op1),
+                    testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op2)
             );
         }
         for (UserPackage pkg : testService.getPackagesWithNonDefaultModes()) {
@@ -275,7 +276,9 @@
                 } else {
                     expectedMode = previousMode;
                 }
-                int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+                int mode =
+                        testService.getUidMode(
+                                uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM);
                 assertEquals(expectedMode, mode);
             }
         }
@@ -284,7 +287,9 @@
         int[] unrelatedUidsInFile = {10225, 10178};
 
         for (int uid : unrelatedUidsInFile) {
-            int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+            int mode =
+                    testService.getUidMode(
+                            uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM);
             assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), mode);
         }
     }
@@ -331,7 +336,9 @@
                 final int uid = UserHandle.getUid(userId, appId);
                 final int expectedMode = AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT);
                 synchronized (testService) {
-                    int mode = testService.getUidMode(uid, OP_USE_FULL_SCREEN_INTENT);
+                    int mode =
+                            testService.getUidMode(
+                                    uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_USE_FULL_SCREEN_INTENT);
                     assertEquals(expectedMode, mode);
                 }
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 10f8510..4958f1c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -44,6 +44,7 @@
 import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_AVOID;
 import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_PREFER;
 import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_UNDEFINED;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,6 +52,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -989,6 +991,7 @@
 
         final ConnectivityController controller = new ConnectivityController(mService,
                 mFlexibilityController);
+        InOrder flexControllerInOrder = inOrder(mFlexibilityController);
 
         final Network meteredNet = mock(Network.class);
         final NetworkCapabilities meteredCaps = createCapabilitiesBuilder().build();
@@ -1042,10 +1045,13 @@
             answerNetwork(generalCallback, redCallback, null, null, null);
             answerNetwork(generalCallback, blueCallback, null, null, null);
 
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
+
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertFalse(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1059,13 +1065,15 @@
 
             generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps);
 
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
             // No transport is specified. Accept the network for transport affinity.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong());
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertTrue(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1073,6 +1081,8 @@
             // No transport is specified. Avoid the network for transport affinity.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertFalse(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1086,12 +1096,17 @@
 
             generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
 
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
+
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
             // No transport is specified. Accept the network for transport affinity.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong());
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertTrue(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1099,6 +1114,8 @@
             // No transport is specified. Avoid the network for transport affinity.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertFalse(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1112,8 +1129,13 @@
 
             generalCallback.onLost(meteredNet);
 
-            assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            // Only the metered network is lost. The unmetered network still satisfies the
+            // affinities.
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
+
+            assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
         }
 
         // Specific UID was blocked
@@ -1123,8 +1145,12 @@
 
             generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
 
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            // No change
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
+
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
         }
 
         // Metered wifi
@@ -1134,10 +1160,13 @@
 
             generalCallback.onCapabilitiesChanged(meteredNet, meteredWifiCaps);
 
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong());
+
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
             // Wifi is preferred.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
@@ -1164,10 +1193,14 @@
 
             generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCelullarCaps);
 
-            assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            // Metered network still has wifi transport
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
+
+            assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
             // Cellular is avoided.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
@@ -1185,6 +1218,14 @@
             assertFalse(blue2.areTransportAffinitiesSatisfied());
         }
 
+        // Remove wifi transport
+        {
+            generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps);
+
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
+        }
+
         // Undefined affinity
         final NetworkCapabilities unmeteredTestCaps = createCapabilitiesBuilder()
                 .addCapability(NET_CAPABILITY_NOT_METERED)
@@ -1198,14 +1239,16 @@
 
             generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredTestCaps);
 
-            assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
             // Undefined is preferred.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong());
             assertTrue(red.areTransportAffinitiesSatisfied());
             assertTrue(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1213,6 +1256,46 @@
             // Undefined is avoided.
             setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true);
             controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
+            assertFalse(red.areTransportAffinitiesSatisfied());
+            assertFalse(blue.areTransportAffinitiesSatisfied());
+            assertFalse(red2.areTransportAffinitiesSatisfied());
+            assertFalse(blue2.areTransportAffinitiesSatisfied());
+        }
+
+        // Lost all networks
+        {
+            // Set network as accepted to help confirm onLost notifies flex controller
+            setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
+            controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong());
+
+            answerNetwork(generalCallback, redCallback, unmeteredNet, null, null);
+            answerNetwork(generalCallback, blueCallback, unmeteredNet, null, null);
+
+            generalCallback.onLost(meteredNet);
+            generalCallback.onLost(unmeteredNet);
+
+            flexControllerInOrder.verify(mFlexibilityController)
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong());
+
+            assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+            setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false);
+            controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
+            assertFalse(red.areTransportAffinitiesSatisfied());
+            assertFalse(blue.areTransportAffinitiesSatisfied());
+            assertFalse(red2.areTransportAffinitiesSatisfied());
+            assertFalse(blue2.areTransportAffinitiesSatisfied());
+            // Undefined is avoided.
+            setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true);
+            controller.onConstantsUpdatedLocked();
+            flexControllerInOrder.verify(mFlexibilityController, never())
+                    .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong());
             assertFalse(red.areTransportAffinitiesSatisfied());
             assertFalse(blue.areTransportAffinitiesSatisfied());
             assertFalse(red2.areTransportAffinitiesSatisfied());
@@ -1275,7 +1358,7 @@
         final ConnectivityController controller = spy(
                 new ConnectivityController(mService, mFlexibilityController));
         doReturn(true).when(controller)
-                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY));
         doReturn(true).when(controller).isNetworkAvailable(any());
         final JobStatus red = createJobStatus(createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
@@ -1318,7 +1401,7 @@
         final ConnectivityController controller = spy(
                 new ConnectivityController(mService, mFlexibilityController));
         doReturn(false).when(controller)
-                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY));
         final JobStatus red = createJobStatus(createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -1388,7 +1471,7 @@
 
         // Both jobs would still be ready. Exception should not be revoked.
         doReturn(true).when(controller)
-                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY));
         doReturn(true).when(controller).isNetworkAvailable(any());
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, never())
@@ -1396,9 +1479,9 @@
 
         // One job is still ready. Exception should not be revoked.
         doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(
-                eq(redOne), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                eq(redOne), eq(CONSTRAINT_CONNECTIVITY));
         doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(
-                eq(redTwo), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                eq(redTwo), eq(CONSTRAINT_CONNECTIVITY));
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, never())
                 .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
@@ -1406,7 +1489,7 @@
 
         // Both jobs are not ready. Exception should be revoked.
         doReturn(false).when(controller)
-                .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+                .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY));
         controller.reevaluateStateLocked(UID_RED);
         inOrder.verify(mNetPolicyManagerInternal, times(1))
                 .setAppIdleWhitelist(eq(UID_RED), eq(false));
@@ -1473,26 +1556,26 @@
         controller.maybeStartTrackingJobLocked(unnetworked, null);
         answerNetwork(callback.getValue(), redCallback.getValue(), null, cellularNet, cellularCaps);
 
-        assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
+        assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
         networked.setStandbyBucket(RESTRICTED_INDEX);
         unnetworked.setStandbyBucket(RESTRICTED_INDEX);
         controller.startTrackingRestrictedJobLocked(networked);
         controller.startTrackingRestrictedJobLocked(unnetworked);
-        assertFalse(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertFalse(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
         // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a
         // connectivity constraint.
-        assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
 
         networked.setStandbyBucket(RARE_INDEX);
         unnetworked.setStandbyBucket(RARE_INDEX);
         controller.stopTrackingRestrictedJobLocked(networked);
         controller.stopTrackingRestrictedJobLocked(unnetworked);
-        assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
         // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a
         // connectivity constraint.
-        assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index ee68b6d..0659f7e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -19,19 +19,25 @@
 import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE;
 import static android.app.job.JobInfo.BIAS_TOP_APP;
 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+import static android.app.job.JobInfo.NETWORK_TYPE_CELLULAR;
+import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
@@ -54,6 +60,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.net.NetworkRequest;
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
@@ -693,15 +700,59 @@
 
     @Test
     public void testTransportAffinity() {
-        JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY);
-        JobStatus js = createJobStatus("testTopAppBypass", jb);
+        JobStatus jsAny = createJobStatus("testTransportAffinity",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
+        JobStatus jsCell = createJobStatus("testTransportAffinity",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_CELLULAR));
+        JobStatus jsWifi = createJobStatus("testTransportAffinity",
+                createJob(0).setRequiredNetwork(
+                        new NetworkRequest.Builder()
+                                .addTransportType(TRANSPORT_WIFI)
+                                .build()));
+        // Disable the unseen constraint logic.
+        mFlexibilityController.setConstraintSatisfied(
+                SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, true, FROZEN_TIME);
+        mFlexibilityController.setConstraintSatisfied(
+                SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME);
+        // Require only a single constraint
+        jsAny.adjustNumRequiredFlexibleConstraints(-3);
+        jsCell.adjustNumRequiredFlexibleConstraints(-2);
+        jsWifi.adjustNumRequiredFlexibleConstraints(-2);
         synchronized (mFlexibilityController.mLock) {
-            js.setTransportAffinitiesSatisfied(false);
-            assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
-            js.setTransportAffinitiesSatisfied(true);
-            assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
-            js.setTransportAffinitiesSatisfied(false);
-            assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+            jsAny.setTransportAffinitiesSatisfied(false);
+            jsCell.setTransportAffinitiesSatisfied(false);
+            jsWifi.setTransportAffinitiesSatisfied(false);
+            mFlexibilityController.setConstraintSatisfied(
+                    CONSTRAINT_CONNECTIVITY, false, FROZEN_TIME);
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
+
+            // A good network exists, but the network hasn't been assigned to any of the jobs
+            jsAny.setTransportAffinitiesSatisfied(false);
+            jsCell.setTransportAffinitiesSatisfied(false);
+            jsWifi.setTransportAffinitiesSatisfied(false);
+            mFlexibilityController.setConstraintSatisfied(
+                    CONSTRAINT_CONNECTIVITY, true, FROZEN_TIME);
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
+
+            // The good network has been assigned to the relevant jobs
+            jsAny.setTransportAffinitiesSatisfied(true);
+            jsCell.setTransportAffinitiesSatisfied(false);
+            jsWifi.setTransportAffinitiesSatisfied(true);
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
+
+            // One job loses access to the network.
+            jsAny.setTransportAffinitiesSatisfied(true);
+            jsCell.setTransportAffinitiesSatisfied(false);
+            jsWifi.setTransportAffinitiesSatisfied(false);
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
         }
     }
 
@@ -768,6 +819,131 @@
     }
 
     @Test
+    public void testHasEnoughSatisfiedConstraints_unseenConstraints_soonAfterBoot() {
+        // Add connectivity to require 4 constraints
+        JobStatus js = createJobStatus("testHasEnoughSatisfiedConstraints",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
+
+        // Too soon after boot
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(100 - 1), ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
+        }
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS - 1),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
+        }
+
+        // Long after boot
+
+        // No constraints ever seen. Don't bother waiting
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
+        }
+    }
+
+    @Test
+    public void testHasEnoughSatisfiedConstraints_unseenConstraints_longAfterBoot() {
+        // Add connectivity to require 4 constraints
+        JobStatus connJs = createJobStatus("testHasEnoughSatisfiedConstraints",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
+        JobStatus nonConnJs = createJobStatus("testHasEnoughSatisfiedConstraints",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_NONE));
+
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_BATTERY_NOT_LOW, true,
+                2 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CHARGING, true,
+                3 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_IDLE, true,
+                4 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, true,
+                5 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+
+        // Long after boot
+        // All constraints satisfied right now
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // Go down to 2 satisfied
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, false,
+                6 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_IDLE, false,
+                7 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        // 3 & 4 constraints were seen recently enough, so the job should wait
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // 4 constraints still in the grace period. Wait.
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(
+                        Instant.ofEpochMilli(16 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // 3 constraints still in the grace period. Wait.
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(
+                        Instant.ofEpochMilli(17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // 3 constraints haven't been seen recently. Don't wait.
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(
+                        Instant.ofEpochMilli(
+                                17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // Add then remove connectivity. Resets expectation of 3 constraints for connectivity jobs.
+        // Connectivity job should wait while the non-connectivity job can run.
+        // of getting back to 4 constraints.
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, true,
+                18 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, false,
+                19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(
+                        Instant.ofEpochMilli(
+                                19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1),
+                        ZoneOffset.UTC);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+    }
+
+    @Test
     public void testResetJobNumDroppedConstraints() {
         JobInfo.Builder jb = createJob(22);
         JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 931b38d..f8fe97e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -22,12 +22,14 @@
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.content.pm.UserInfo
 import android.os.Build
+import android.os.UserHandle
 import android.os.UserHandle.USER_SYSTEM
 import android.util.Log
 import com.android.server.testutils.any
 import com.android.server.testutils.spy
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -177,4 +179,13 @@
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_INTERNAL_ERROR)
     }
+
+    @Test
+    fun deletePackageLIFWithNonExistantPackage_isFalse() {
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+                                      mock(BroadcastHelper::class.java))
+        val result = dph.deletePackageLIF("a.nonexistent.package", UserHandle.of(USER_SYSTEM), true,
+                                          intArrayOf(0), 0, PackageRemovedInfo(), true)
+        assertFalse(result)
+    }
 }
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 be33b1b..46806f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -55,6 +55,8 @@
 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.pkg.ParsedPackage
+import com.android.internal.pm.pkg.parsing.ParsingPackage
 import com.android.server.LocalManagerRegistry
 import com.android.server.LocalServices
 import com.android.server.LockGuard
@@ -66,10 +68,8 @@
 import com.android.server.pm.dex.DynamicCodeLogger
 import com.android.server.pm.parsing.PackageParser2
 import com.android.server.pm.parsing.pkg.PackageImpl
-import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.parsing.ParsingPackage
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.snapshot.PackageDataSnapshot
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 1e65c89..733a433 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -64,6 +65,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -80,6 +82,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 @SmallTest
 @Presubmit
@@ -113,6 +116,8 @@
     @Mock
     private PackageStateInternal mPackageState;
     @Mock
+    private PackageStateInternal mCallerPackageState;
+    @Mock
     private Bitmap mIcon;
 
     private final InstallSource mInstallSource =
@@ -154,6 +159,11 @@
                 mPackageState);
         when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(),
                 anyInt())).thenReturn(mock(PackageStateInternal.class));
+        when(mComputer.getPackageStateFiltered(eq(CALLER_PACKAGE), anyInt(), anyInt())).thenReturn(
+                mCallerPackageState);
+        AndroidPackage androidPackage = mock(AndroidPackage.class);
+        when(mCallerPackageState.getAndroidPackage()).thenReturn(androidPackage);
+        when(androidPackage.getRequestedPermissions()).thenReturn(Set.of());
         when(mPackageState.getPackageName()).thenReturn(PACKAGE);
         when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
         mPackageSetting = createBasicPackageSetting();
@@ -171,6 +181,12 @@
 
         when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
         when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
+                PackageManager.PERMISSION_DENIED);
 
         when(mAppOpsManager.checkOp(
                 eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
@@ -182,6 +198,10 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
                 mock(Resources.class));
+        when(mInstallerService.createSessionInternal(any(), any(), any(), anyInt(),
+                anyInt())).thenReturn(1);
+        when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn(
+                PackageInstaller.SessionInfo.INVALID_ID);
         doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
                 .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
                         eq(mUserId));
@@ -382,7 +402,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, "different",
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -400,7 +420,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -412,7 +432,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s is not currently archived.", PACKAGE));
@@ -424,7 +444,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s is not currently archived.", PACKAGE));
@@ -448,7 +468,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
-                        UserHandle.CURRENT));
+                        mIntentSender, UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("No installer found to unarchive app %s.", PACKAGE));
@@ -458,7 +478,8 @@
     public void unarchiveApp_success() {
         mUserState.setArchiveState(createArchiveState()).setInstalled(false);
 
-        mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+        mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -475,6 +496,7 @@
                 /* initialExtras= */ isNull());
         Intent intent = intentCaptor.getValue();
         assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
+        assertThat(intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, -1)).isEqualTo(1);
         assertThat(intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
                 PACKAGE);
         assertThat(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index b8f726b..e685c3f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -25,13 +25,13 @@
 import android.os.UserHandle
 import android.util.ArrayMap
 import android.util.PackageUtils
+import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.server.SystemConfig.SharedLibraryEntry
 import com.android.server.compat.PlatformCompat
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.parsing.pkg.PackageImpl
-import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
 import com.android.server.testutils.mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/OWNERS b/services/tests/mockingservicestests/src/com/android/server/power/OWNERS
index fb62520..37396f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/power/OWNERS
@@ -1,3 +1,3 @@
 include /services/core/java/com/android/server/power/OWNERS
 
-per-file ThermalManagerServiceMockingTest.java=wvw@google.com,xwxw@google.com
+per-file ThermalManagerServiceMockingTest.java=file:/THERMAL_OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 3dbab13..15ae463 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -453,7 +453,7 @@
         doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
                 .when(sContext).sendBroadcastAsUser(any(), any());
         doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
-                .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+                .when(mService).notifyWallpaperColorsChanged(wallpaper);
 
         assertNull(wallpaper.wallpaperObserver);
         mService.switchUser(wallpaper.userId, null);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
index 2003d04..ca7de7c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -90,6 +90,10 @@
 
     private AggregatedPowerStats prepareAggregatePowerStats() {
         AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+
+        PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+        stats.addPowerStats(ps, 0);
+
         stats.addClockUpdate(1000, 456);
         stats.setDuration(789);
 
@@ -100,7 +104,6 @@
         stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
 
-        PowerStats ps = new PowerStats(mPowerComponentDescriptor);
         ps.stats[0] = 100;
         ps.stats[1] = 987;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 663af5d..9c2834d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -215,7 +215,7 @@
 
     public class TestBatteryStatsImpl extends BatteryStatsImpl {
         public TestBatteryStatsImpl(Context context) {
-            super(Clock.SYSTEM_CLOCK, null);
+            super(Clock.SYSTEM_CLOCK, null, null, null);
             mPowerProfile = new PowerProfile(context, true /* forTest */);
 
             SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
index 55ffa1a..f9f32b2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
@@ -37,6 +37,8 @@
 import static org.mockito.Mockito.when;
 
 import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
@@ -97,6 +99,7 @@
     BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
 
     private MockClock mClocks;
+    private PowerStatsUidResolver mPowerStatsUidResolver;
     private MockBatteryStatsImpl mBatteryStatsImpl;
     private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
 
@@ -105,7 +108,9 @@
         MockitoAnnotations.initMocks(this);
 
         mClocks = new MockClock();
-        mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
+        Handler handler = new Handler(Looper.getMainLooper());
+        mPowerStatsUidResolver = new PowerStatsUidResolver();
+        mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks, null, handler, mPowerStatsUidResolver)
                 .setTestCpuScalingPolicies()
                 .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
                 .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
@@ -374,7 +379,7 @@
 
         // PRECONDITIONS
         final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
-        mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+        mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid);
         final long[][] deltasUs = {
                 {9379, 3332409833484L}, {493247, 723234}, {3247819, 123348}
         };
@@ -965,7 +970,7 @@
 
         // PRECONDITIONS
         final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
-        mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+        mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid);
         final long[][] deltasMs = {
                 {3, 12, 55, 100, 32},
                 {32483274, 232349349, 123, 2398, 0},
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 5ebc6ca..8d51592 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -39,14 +39,22 @@
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BluetoothBatteryStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Parcel;
 import android.os.WakeLockStats;
 import android.os.WorkSource;
 import android.util.SparseArray;
 import android.view.Display;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -65,6 +73,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.time.Instant;
 import java.util.List;
 
 @LargeTest
@@ -93,6 +103,11 @@
 
     private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStatsImpl;
+    private Handler mHandler;
+    private PowerStatsStore mPowerStatsStore;
+    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    @Mock
+    private PowerStatsExporter mPowerStatsExporter;
 
     @Before
     public void setUp() {
@@ -103,12 +118,23 @@
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
         when(mKernelWakelockReader.readKernelWakelockStats(
                 any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats);
-        mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        mHandler = new Handler(bgThread.getLooper());
+        mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, null, mHandler)
                 .setPowerProfile(mPowerProfile)
                 .setCpuScalingPolicies(mCpuScalingPolicies)
                 .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
                 .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
                 .setKernelWakelockReader(mKernelWakelockReader);
+
+        final Context context = InstrumentationRegistry.getContext();
+        File systemDir = context.getCacheDir();
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler,
+                new AggregatedPowerStatsConfig());
+        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerStatsExporter,
+                mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore,
+                mMockClock);
     }
 
     @Test
@@ -754,4 +780,76 @@
         parcel.recycle();
         return info;
     }
+
+    @Test
+    public void storeBatteryUsageStatsOnReset() {
+        mBatteryStatsImpl.forceRecordAllHistory();
+
+        mMockClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+        mMockClock.realtime = 7654321;
+
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.setOnBatteryLocked(mMockClock.realtime, mMockClock.uptime, true,
+                    BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+            // Will not save to PowerStatsStore because "saveBatteryUsageStatsOnReset" has not
+            // been called yet.
+            mBatteryStatsImpl.resetAllStatsAndHistoryLocked(
+                    BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+        }
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider,
+                mPowerStatsStore);
+
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime);
+        }
+
+        mMockClock.realtime += 60000;
+        mMockClock.currentTime += 60000;
+
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.noteFlashlightOffLocked(42, mMockClock.realtime, mMockClock.uptime);
+        }
+
+        mMockClock.realtime += 60000;
+        mMockClock.currentTime += 60000;
+
+        // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.resetAllStatsAndHistoryLocked(
+                    BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+        }
+
+        // Await completion
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(1);
+
+        PowerStatsSpan.Metadata metadata = contents.get(0);
+
+        PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+                BatteryUsageStatsSection.TYPE);
+        assertThat(span).isNotNull();
+
+        List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+        assertThat(timeFrames).hasSize(1);
+        assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+        assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+        List<PowerStatsSpan.Section> sections = span.getSections();
+        assertThat(sections).hasSize(1);
+
+        PowerStatsSpan.Section section = sections.get(0);
+        assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+        BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+        assertThat(bus.getAggregateBatteryConsumer(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+                .isEqualTo(60000);
+    }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 7ef1a3f..24c67f8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -35,6 +35,8 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -155,7 +157,9 @@
     @SmallTest
     public void testNoteStartWakeLocked_isolatedUid() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
-        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
+                new Handler(Looper.getMainLooper()), uidResolver);
 
         int pid = 10;
         String name = "name";
@@ -165,7 +169,7 @@
         isolatedWorkChain.addNode(ISOLATED_UID, name);
 
         // Map ISOLATED_UID to UID.
-        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+        uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID);
 
         bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
@@ -195,7 +199,9 @@
     @SmallTest
     public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
-        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
+                new Handler(Looper.getMainLooper()), uidResolver);
 
         int pid = 10;
         String name = "name";
@@ -205,7 +211,7 @@
         isolatedWorkChain.addNode(ISOLATED_UID, name);
 
         // Map ISOLATED_UID to UID.
-        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+        uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID);
 
         bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
@@ -216,7 +222,7 @@
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
 
         clocks.realtime = clocks.uptime = 150;
-        bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+        uidResolver.releaseIsolatedUid(ISOLATED_UID);
 
         clocks.realtime = clocks.uptime = 220;
         bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
@@ -237,8 +243,9 @@
     @SmallTest
     public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
-        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
-
+        PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
+                new Handler(Looper.getMainLooper()), uidResolver);
 
         bi.setRecordAllHistoryLocked(true);
         bi.forceRecordAllHistory();
@@ -251,7 +258,7 @@
         isolatedWorkChain.addNode(ISOLATED_UID, name);
 
         // Map ISOLATED_UID to UID.
-        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+        uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID);
 
         bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
@@ -290,8 +297,9 @@
     @SmallTest
     public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
-        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
-
+        PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
+                new Handler(Looper.getMainLooper()), uidResolver);
 
         bi.setRecordAllHistoryLocked(true);
         bi.forceRecordAllHistory();
@@ -304,7 +312,7 @@
         isolatedWorkChain.addNode(ISOLATED_UID, name);
 
         // Map ISOLATED_UID to UID.
-        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+        uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID);
 
         bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
@@ -314,7 +322,7 @@
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
 
         clocks.realtime = clocks.uptime = 150;
-        bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+        uidResolver.releaseIsolatedUid(ISOLATED_UID);
 
         clocks.realtime = clocks.uptime = 220;
         bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index b1da1fc..7148b16 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -28,9 +28,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
+import android.os.ConditionVariable;
 import android.os.Parcel;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
@@ -40,7 +38,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 
 import org.junit.Rule;
@@ -72,10 +69,11 @@
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
         Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
-                provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
+                provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
 
         final List<UidBatteryConsumer> uidBatteryConsumers =
                 batteryUsageStats.getUidBatteryConsumers();
@@ -99,10 +97,11 @@
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
         Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
-                provider.getBatteryUsageStats(
+                provider.getBatteryUsageStats(batteryStats,
                         new BatteryUsageStatsQuery.Builder()
                                 .includePowerComponents(
                                         new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO})
@@ -204,10 +203,11 @@
         }
 
         Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
-                provider.getBatteryUsageStats(
+                provider.getBatteryUsageStats(batteryStats,
                         new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
 
         Parcel in = Parcel.obtain();
@@ -292,11 +292,11 @@
         }
 
         Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider
-                provider = new BatteryUsageStatsProvider(context, batteryStats);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
-                provider.getBatteryUsageStats(
+                provider.getBatteryUsageStats(batteryStats,
                         new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
 
         Parcel parcel = Parcel.obtain();
@@ -352,27 +352,22 @@
 
     @Test
     public void shouldUpdateStats() {
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                mStatsRule.getBatteryStats());
-
         final List<BatteryUsageStatsQuery> queries = List.of(
                 new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(),
                 new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build()
         );
 
-        mStatsRule.setTime(10500, 0);
-        assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse();
+        assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries,
+                10500, 10000)).isFalse();
 
-        mStatsRule.setTime(11500, 0);
-        assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue();
+        assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries,
+                11500, 10000)).isTrue();
     }
 
     @Test
     public void testAggregateBatteryStats() {
         Context context = InstrumentationRegistry.getContext();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
-        MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
 
         setTime(5 * MINUTE_IN_MS);
         synchronized (batteryStats) {
@@ -381,14 +376,17 @@
 
         PowerStatsStore powerStatsStore = new PowerStatsStore(
                 new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
-                new TestHandler(), null);
+                mStatsRule.getHandler(), null);
+        powerStatsStore.reset();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, powerStatsStore);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
+                mMockClock);
 
-        batteryStats.setBatteryResetListener(reason ->
-                powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
-                        provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
+        batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+        synchronized (batteryStats) {
+            batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+        }
 
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -441,11 +439,16 @@
         }
         setTime(95 * MINUTE_IN_MS);
 
+        // Await completion
+        ConditionVariable done = new ConditionVariable();
+        mStatsRule.getHandler().post(done::open);
+        done.block();
+
         // Include the first and the second snapshot, but not the third or current
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS)
                 .build();
-        final BatteryUsageStats stats = provider.getBatteryUsageStats(query);
+        final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query);
 
         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
         assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS);
@@ -499,30 +502,19 @@
         when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
                 .thenReturn(span1);
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, powerStatsStore);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
+                mMockClock);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 3000)
                 .build();
-        final BatteryUsageStats stats = provider.getBatteryUsageStats(query);
+        final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query);
         assertThat(stats.getCustomPowerComponentNames())
                 .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
         assertThat(stats.getStatsDuration()).isEqualTo(1234);
     }
 
-    private static class TestHandler extends Handler {
-        TestHandler() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            msg.getCallback().run();
-            return true;
-        }
-    }
-
     private static final Random sRandom = new Random();
 
     /**
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 0b10954..e61dd0b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -28,6 +28,8 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
 import android.util.SparseArray;
@@ -57,6 +59,7 @@
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
     private final MockBatteryStatsImpl mBatteryStats;
+    private final Handler mHandler;
 
     private BatteryUsageStats mBatteryUsageStats;
     private boolean mScreenOn;
@@ -73,10 +76,13 @@
     }
 
     public BatteryUsageStatsRule(long currentTime, File historyDir) {
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        mHandler = new Handler(bgThread.getLooper());
         mContext = InstrumentationRegistry.getContext();
         mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
         mMockClock.currentTime = currentTime;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
+        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
         mBatteryStats.setPowerProfile(mPowerProfile);
 
         mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
@@ -92,6 +98,10 @@
         return mMockClock;
     }
 
+    public Handler getHandler() {
+        return mHandler;
+    }
+
     public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
         mPowerProfile.forceInitForTesting(mContext, xmlId);
         return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 79084cc..8ca4ff6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -192,7 +192,7 @@
 
     private static class MockPowerComponentAggregatedPowerStats extends
             PowerComponentAggregatedPowerStats {
-        private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+        private final CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout;
         private final PowerStats.Descriptor mDescriptor;
         private HashMap<String, long[]> mDeviceStats = new HashMap<>();
         private HashMap<String, long[]> mUidStats = new HashMap<>();
@@ -203,10 +203,10 @@
         MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config,
                 boolean useEnergyConsumers) {
             super(config);
-            mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+            mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
             mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3);
             mStatsLayout.addDeviceSectionCpuTimeByCluster(2);
-            mStatsLayout.addDeviceSectionUptime();
+            mStatsLayout.addDeviceSectionUsageDuration();
             if (useEnergyConsumers) {
                 mStatsLayout.addDeviceSectionEnergyConsumers(2);
             }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index bc211df..64d5414 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -19,6 +19,7 @@
 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.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -56,6 +57,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CpuPowerStatsCollectorTest {
+    private static final int ISOLATED_UID = 99123;
+    private static final int UID_1 = 42;
+    private static final int UID_2 = 99;
     private Context mContext;
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
@@ -63,6 +67,8 @@
     private PowerStats mCollectedStats;
     private PowerProfile mPowerProfile;
     @Mock
+    private PowerStatsUidResolver mUidResolver;
+    @Mock
     private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
     @Mock
     private PowerStatsInternal mPowerStatsInternal;
@@ -76,6 +82,14 @@
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
         when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true);
+        when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
+            int uid = invocation.getArgument(0);
+            if (uid == ISOLATED_UID) {
+                return UID_2;
+            } else {
+                return uid;
+            }
+        });
     }
 
     @Test
@@ -156,14 +170,14 @@
         assertThat(descriptor.name).isEqualTo("cpu");
         assertThat(descriptor.statsArrayLength).isEqualTo(13);
         assertThat(descriptor.uidStatsArrayLength).isEqualTo(5);
-        CpuPowerStatsCollector.StatsArrayLayout layout =
-                new CpuPowerStatsCollector.StatsArrayLayout();
+        CpuPowerStatsCollector.CpuStatsArrayLayout layout =
+                new CpuPowerStatsCollector.CpuStatsArrayLayout();
         layout.fromExtras(descriptor.extras);
 
         long[] deviceStats = new long[descriptor.statsArrayLength];
         layout.setTimeByScalingStep(deviceStats, 2, 42);
         layout.setConsumedEnergy(deviceStats, 1, 43);
-        layout.setUptime(deviceStats, 44);
+        layout.setUsageDuration(deviceStats, 44);
         layout.setDevicePowerEstimate(deviceStats, 45);
 
         long[] uidStats = new long[descriptor.uidStatsArrayLength];
@@ -173,10 +187,10 @@
         assertThat(layout.getCpuScalingStepCount()).isEqualTo(7);
         assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42);
 
-        assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2);
+        assertThat(layout.getEnergyConsumerCount()).isEqualTo(2);
         assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43);
 
-        assertThat(layout.getUptime(deviceStats)).isEqualTo(44);
+        assertThat(layout.getUsageDuration(deviceStats)).isEqualTo(44);
 
         assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45);
 
@@ -195,14 +209,15 @@
         mockEnergyConsumers();
 
         CpuPowerStatsCollector collector = createCollector(8, 0);
-        CpuPowerStatsCollector.StatsArrayLayout layout =
-                new CpuPowerStatsCollector.StatsArrayLayout();
+        CpuPowerStatsCollector.CpuStatsArrayLayout layout =
+                new CpuPowerStatsCollector.CpuStatsArrayLayout();
         layout.fromExtras(collector.getPowerStatsDescriptor().extras);
 
         mockKernelCpuStats(new long[]{1111, 2222, 3333},
                 new SparseArray<>() {{
-                    put(42, new long[]{100, 200});
-                    put(99, new long[]{300, 600});
+                    put(UID_1, new long[]{100, 200});
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{200, 450});
                 }}, 0, 1234);
 
         mMockClock.uptime = 1000;
@@ -219,19 +234,19 @@
         assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0);
         assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0);
 
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0))
                 .isEqualTo(100);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1))
                 .isEqualTo(200);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
                 .isEqualTo(300);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
                 .isEqualTo(600);
 
         mockKernelCpuStats(new long[]{5555, 4444, 3333},
                 new SparseArray<>() {{
-                    put(42, new long[]{123, 234});
-                    put(99, new long[]{345, 678});
+                    put(UID_1, new long[]{123, 234});
+                    put(ISOLATED_UID, new long[]{245, 528});
                 }}, 1234, 3421);
 
         mMockClock.uptime = 2000;
@@ -249,13 +264,13 @@
         // 700 * 1000 / 3500
         assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200);
 
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0))
                 .isEqualTo(23);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1))
                 .isEqualTo(34);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
                 .isEqualTo(45);
-        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
                 .isEqualTo(78);
     }
 
@@ -282,9 +297,9 @@
     private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
             int defaultCpuPowerBracketsPerEnergyConsumer) {
         CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies,
-                mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal,
-                () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets,
-                defaultCpuPowerBracketsPerEnergyConsumer);
+                mPowerProfile, mHandler, mMockKernelCpuStatsReader, mUidResolver,
+                () -> mPowerStatsInternal, () -> 3500, 60_000, mMockClock,
+                defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer);
         collector.addConsumer(stats -> mCollectedStats = stats);
         collector.setEnabled(true);
         return collector;
@@ -375,8 +390,8 @@
     }
 
     private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) {
-        CpuPowerStatsCollector.StatsArrayLayout layout =
-                new CpuPowerStatsCollector.StatsArrayLayout();
+        CpuPowerStatsCollector.CpuStatsArrayLayout layout =
+                new CpuPowerStatsCollector.CpuStatsArrayLayout();
         layout.fromExtras(collector.getPowerStatsDescriptor().extras);
         return layout.getScalingStepToPowerBracketMap();
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 4150972a..fb71ac8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -61,16 +61,23 @@
     }
 
     MockBatteryStatsImpl(Clock clock, File historyDirectory) {
-        super(clock, historyDirectory);
+        this(clock, historyDirectory, new Handler(Looper.getMainLooper()));
+    }
+
+    MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler) {
+        this(clock, historyDirectory, handler, new PowerStatsUidResolver());
+    }
+
+    MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler,
+            PowerStatsUidResolver powerStatsUidResolver) {
+        super(clock, historyDirectory, handler, powerStatsUidResolver);
         initTimersAndCounters();
         setMaxHistoryBuffer(128 * 1024);
 
         setExternalStatsSyncLocked(mExternalStatsSync);
         informThatAllExternalStatsAreFlushed();
 
-        // A no-op handler.
-        mHandler = new Handler(Looper.getMainLooper()) {
-        };
+        mHandler = handler;
 
         mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class);
         mKernelWakelockReader = null;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index b52fc8a..6704987 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -76,9 +76,18 @@
 
     @Test
     public void stateUpdates() {
+        PowerStats.Descriptor descriptor =
+                new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+                        new PersistableBundle());
+        PowerStats powerStats = new PowerStats(descriptor);
+
         mClock.currentTime = 1222156800000L;    // An important date in world history
 
         mHistory.forceRecordAllHistory();
+        powerStats.stats = new long[]{0};
+        powerStats.uidStats.put(TEST_UID, new long[]{0});
+        mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
         mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
@@ -87,10 +96,6 @@
 
         advance(1000);
 
-        PowerStats.Descriptor descriptor =
-                new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
-                        new PersistableBundle());
-        PowerStats powerStats = new PowerStats(descriptor);
         powerStats.stats = new long[]{10000};
         powerStats.uidStats.put(TEST_UID, new long[]{1234});
         mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
@@ -181,17 +186,21 @@
 
     @Test
     public void incompatiblePowerStats() {
+        PowerStats.Descriptor descriptor =
+                new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+                        new PersistableBundle());
+        PowerStats powerStats = new PowerStats(descriptor);
+
         mHistory.forceRecordAllHistory();
+        powerStats.stats = new long[]{0};
+        powerStats.uidStats.put(TEST_UID, new long[]{0});
+        mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
         mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND);
 
         advance(1000);
 
-        PowerStats.Descriptor descriptor =
-                new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
-                        new PersistableBundle());
-        PowerStats powerStats = new PowerStats(descriptor);
         powerStats.stats = new long[]{10000};
         powerStats.uidStats.put(TEST_UID, new long[]{1234});
         mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
new file mode 100644
index 0000000..3c48262
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.power.stats;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import android.os.AggregateBatteryConsumer;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsExporterTest {
+
+    private static final int APP_UID1 = 42;
+    private static final int APP_UID2 = 84;
+    private static final double TOLERANCE = 0.01;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setCpuScalingPolicy(0, new int[]{0}, new int[]{100})
+            .setAveragePowerForCpuScalingPolicy(0, 360)
+            .setAveragePowerForCpuScalingStep(0, 0, 300)
+            .setCpuPowerBracketCount(1)
+            .setCpuPowerBracket(0, 0, 0);
+
+    private MockClock mClock = new MockClock();
+    private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+    private PowerStatsStore mPowerStatsStore;
+    private PowerStatsAggregator mPowerStatsAggregator;
+    private BatteryStatsHistory mHistory;
+    private CpuPowerStatsCollector.CpuStatsArrayLayout mCpuStatsArrayLayout;
+    private PowerStats.Descriptor mPowerStatsDescriptor;
+
+    @Before
+    public void setup() {
+        File storeDirectory = new File(getContext().getCacheDir(), getClass().getSimpleName());
+        clearDirectory(storeDirectory);
+
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+                .trackDeviceStates(AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(
+                        new CpuAggregatedPowerStatsProcessor(mStatsRule.getPowerProfile(),
+                                mStatsRule.getCpuScalingPolicies()));
+
+        mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config);
+        mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000,
+                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+                mMonotonicClock, null);
+        mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory);
+
+        mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
+        mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1);
+        mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1);
+        mCpuStatsArrayLayout.addDeviceSectionPowerEstimate();
+        mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0});
+        mCpuStatsArrayLayout.addUidSectionPowerEstimate();
+        PersistableBundle extras = new PersistableBundle();
+        mCpuStatsArrayLayout.toExtras(extras);
+
+        mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
+                mCpuStatsArrayLayout.getDeviceStatsArrayLength(),
+                mCpuStatsArrayLayout.getUidStatsArrayLength(), extras);
+    }
+
+    @Test
+    public void breakdownByProcState_fullRange() throws Exception {
+        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
+                new String[0], /* includePowerModels */ false,
+                /* includeProcessStateData */ true, /* powerThreshold */ 0);
+        exportAggregatedPowerStats(builder, 1000, 10000);
+
+        BatteryUsageStats actual = builder.build();
+        String message = "Actual BatteryUsageStats: " + actual;
+
+        assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+        assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 13.5);
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND, 7.47);
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND, 6.03);
+
+        assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 12.03);
+        assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 12.03);
+
+        actual.close();
+    }
+
+    @Test
+    public void breakdownByProcState_subRange() throws Exception {
+        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
+                new String[0], /* includePowerModels */ false,
+                /* includeProcessStateData */ true, /* powerThreshold */ 0);
+        exportAggregatedPowerStats(builder, 3700, 6700);
+
+        BatteryUsageStats actual = builder.build();
+        String message = "Actual BatteryUsageStats: " + actual;
+
+        assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4);
+        assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4);
+
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 4.06);
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND, 1.35);
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND, 2.70);
+
+        assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 11.33);
+        assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 11.33);
+
+        actual.close();
+    }
+
+    @Test
+    public void combinedProcessStates() throws Exception {
+        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
+                new String[0], /* includePowerModels */ false,
+                /* includeProcessStateData */ false, /* powerThreshold */ 0);
+        exportAggregatedPowerStats(builder, 1000, 10000);
+
+        BatteryUsageStats actual = builder.build();
+        String message = "Actual BatteryUsageStats: " + actual;
+
+        assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+        assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+
+        assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 13.5);
+        assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_ANY, 12.03);
+        UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream()
+                .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null);
+        // There shouldn't be any per-procstate data
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
+                        BatteryConsumer.POWER_COMPONENT_CPU,
+                        BatteryConsumer.PROCESS_STATE_FOREGROUND)));
+
+
+        actual.close();
+    }
+
+    private void recordBatteryHistory() {
+        PowerStats powerStats = new PowerStats(mPowerStatsDescriptor);
+        long[] uidStats1 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()];
+        powerStats.uidStats.put(APP_UID1, uidStats1);
+        long[] uidStats2 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()];
+        powerStats.uidStats.put(APP_UID2, uidStats2);
+
+        mHistory.forceRecordAllHistory();
+
+        mHistory.startRecordingHistory(1000, 1000, false);
+        mHistory.recordPowerStats(1000, 1000, powerStats);
+        mHistory.recordBatteryState(1000, 1000, 70, /* plugged */ false);
+        mHistory.recordStateStartEvent(1000, 1000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+        mHistory.recordProcessStateChange(1000, 1000, APP_UID1,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        mHistory.recordProcessStateChange(1000, 1000, APP_UID2,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 11111);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 10000);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 1111);
+        mHistory.recordPowerStats(1000, 1000, powerStats);
+
+        mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 12345);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 9876);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 2469);
+        mHistory.recordPowerStats(3000, 3000, powerStats);
+
+        mPowerStatsAggregator.aggregatePowerStats(0, 3500, stats -> {
+            mPowerStatsStore.storeAggregatedPowerStats(stats);
+        });
+
+        mHistory.recordProcessStateChange(4000, 4000, APP_UID1,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+
+        mHistory.recordStateStopEvent(4000, 4000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+
+        mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 54321);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 14321);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 40000);
+        mHistory.recordPowerStats(6000, 6000, powerStats);
+
+        mPowerStatsAggregator.aggregatePowerStats(3500, 6500, stats -> {
+            mPowerStatsStore.storeAggregatedPowerStats(stats);
+        });
+
+        mHistory.recordStateStartEvent(7000, 7000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+        mHistory.recordProcessStateChange(7000, 7000, APP_UID1,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        mHistory.recordProcessStateChange(7000, 7000, APP_UID2,
+                BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
+
+        mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 23456);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 23456);
+        mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 0);
+        mHistory.recordPowerStats(8000, 8000, powerStats);
+
+        assertThat(mPowerStatsStore.getTableOfContents()).hasSize(2);
+    }
+
+    private void exportAggregatedPowerStats(BatteryUsageStats.Builder builder,
+            int monotonicStartTime, int monotonicEndTime) {
+        recordBatteryHistory();
+        PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
+                mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
+        exporter.exportAggregatedPowerStats(builder, monotonicStartTime, monotonicEndTime);
+    }
+
+    private void assertDevicePowerEstimate(String message, BatteryUsageStats bus, int componentId,
+            double expected) {
+        AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        assertWithMessage(message).that(consumer.getConsumedPower(componentId))
+                .isWithin(TOLERANCE).of(expected);
+    }
+
+    private void assertAllAppsPowerEstimate(String message, BatteryUsageStats bus, int componentId,
+            double expected) {
+        AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+        assertWithMessage(message).that(consumer.getConsumedPower(componentId))
+                .isWithin(TOLERANCE).of(expected);
+    }
+
+    private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid,
+            int componentId, int processState, double expected) {
+        List<UidBatteryConsumer> uidScopes = bus.getUidBatteryConsumers();
+        final UidBatteryConsumer uidScope = uidScopes.stream()
+                .filter(us -> us.getUid() == uid).findFirst().orElse(null);
+        assertWithMessage(message).that(uidScope).isNotNull();
+        assertWithMessage(message).that(uidScope.getConsumedPower(
+                new BatteryConsumer.Dimensions(componentId, processState)))
+                .isWithin(TOLERANCE).of(expected);
+    }
+
+    private void clearDirectory(File dir) {
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    clearDirectory(child);
+                }
+                child.delete();
+            }
+        }
+    }
+
+    private static class TestHandler extends Handler {
+        TestHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            msg.getCallback().run();
+            return true;
+        }
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
index 0e58787..7257a94 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -27,9 +27,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.os.BatteryConsumer;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -59,13 +56,13 @@
     private MockClock mClock = new MockClock();
     private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
     private MockBatteryStatsImpl mBatteryStats;
-    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
     private PowerStatsScheduler mPowerStatsScheduler;
     private PowerProfile mPowerProfile;
     private PowerStatsAggregator mPowerStatsAggregator;
     private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
 
     @Before
+    @SuppressWarnings("GuardedBy")
     public void setup() {
         final Context context = InstrumentationRegistry.getContext();
 
@@ -83,11 +80,10 @@
         mPowerProfile = mock(PowerProfile.class);
         when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
         mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
         mPowerStatsAggregator = mock(PowerStatsAggregator.class);
         mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
                 TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
-                mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+                mMonotonicClock, mHandler, mBatteryStats);
     }
 
     @Test
@@ -176,70 +172,6 @@
     }
 
     @Test
-    public void storeBatteryUsageStatsOnReset() {
-        mBatteryStats.forceRecordAllHistory();
-        synchronized (mBatteryStats) {
-            mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
-                    BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
-        }
-
-        mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
-
-        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
-
-        mPowerStatsScheduler.start(true);
-
-        synchronized (mBatteryStats) {
-            mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
-        }
-
-        mClock.realtime += 60000;
-        mClock.currentTime += 60000;
-
-        synchronized (mBatteryStats) {
-            mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
-        }
-
-        mClock.realtime += 60000;
-        mClock.currentTime += 60000;
-
-        // Battery stats reset should have the side-effect of saving accumulated battery usage stats
-        synchronized (mBatteryStats) {
-            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-        }
-
-        // Await completion
-        ConditionVariable done = new ConditionVariable();
-        mHandler.post(done::open);
-        done.block();
-
-        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
-        assertThat(contents).hasSize(1);
-
-        PowerStatsSpan.Metadata metadata = contents.get(0);
-
-        PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
-                BatteryUsageStatsSection.TYPE);
-        assertThat(span).isNotNull();
-
-        List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
-        assertThat(timeFrames).hasSize(1);
-        assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
-        assertThat(timeFrames.get(0).duration).isEqualTo(120000);
-
-        List<PowerStatsSpan.Section> sections = span.getSections();
-        assertThat(sections).hasSize(1);
-
-        PowerStatsSpan.Section section = sections.get(0);
-        assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
-        BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
-        assertThat(bus.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isEqualTo(60000);
-    }
-
-    @Test
     public void alignToWallClock() {
         // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
         assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java
new file mode 100644
index 0000000..60b2541
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsUidResolverTest {
+
+    private final PowerStatsUidResolver mResolver = new PowerStatsUidResolver();
+    @Mock
+    PowerStatsUidResolver.Listener mListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void addAndRemoveIsolatedUid() {
+        mResolver.addListener(mListener);
+        mResolver.noteIsolatedUidAdded(42, 314);
+        verify(mListener).onIsolatedUidAdded(42, 314);
+        assertThat(mResolver.mapUid(42)).isEqualTo(314);
+
+        mResolver.noteIsolatedUidRemoved(42, 314);
+        verify(mListener).onBeforeIsolatedUidRemoved(42, 314);
+        verify(mListener).onAfterIsolatedUidRemoved(42, 314);
+        assertThat(mResolver.mapUid(42)).isEqualTo(42);
+
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void retainAndRemoveIsolatedUid() {
+        mResolver.addListener(mListener);
+        mResolver.noteIsolatedUidAdded(42, 314);
+        verify(mListener).onIsolatedUidAdded(42, 314);
+        assertThat(mResolver.mapUid(42)).isEqualTo(314);
+
+        mResolver.retainIsolatedUid(42);
+
+        mResolver.noteIsolatedUidRemoved(42, 314);
+        verify(mListener).onBeforeIsolatedUidRemoved(42, 314);
+        assertThat(mResolver.mapUid(42)).isEqualTo(314);
+        verifyNoMoreInteractions(mListener);
+
+        mResolver.releaseIsolatedUid(42);
+        verify(mListener).onAfterIsolatedUidRemoved(42, 314);
+        assertThat(mResolver.mapUid(42)).isEqualTo(42);
+
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void removeUidsInRange() {
+        mResolver.noteIsolatedUidAdded(1, 314);
+        mResolver.noteIsolatedUidAdded(2, 314);
+        mResolver.noteIsolatedUidAdded(3, 314);
+        mResolver.noteIsolatedUidAdded(4, 314);
+        mResolver.noteIsolatedUidAdded(6, 314);
+        mResolver.noteIsolatedUidAdded(8, 314);
+        mResolver.noteIsolatedUidAdded(10, 314);
+
+        mResolver.addListener(mListener);
+
+        mResolver.releaseUidsInRange(4, 4);     // Single
+        verify(mListener).onAfterIsolatedUidRemoved(4, 314);
+        verifyNoMoreInteractions(mListener);
+
+        // Now: [1, 2, 3, 6, 8, 10]
+
+        mResolver.releaseUidsInRange(2, 3);     // Inclusive
+        verify(mListener).onAfterIsolatedUidRemoved(2, 314);
+        verify(mListener).onAfterIsolatedUidRemoved(3, 314);
+        verifyNoMoreInteractions(mListener);
+
+        // Now: [1, 6, 8, 10]
+
+        mResolver.releaseUidsInRange(5, 9);     // Exclusive
+        verify(mListener).onAfterIsolatedUidRemoved(6, 314);
+        verify(mListener).onAfterIsolatedUidRemoved(8, 314);
+        verifyNoMoreInteractions(mListener);
+
+        // Now: [1, 10]
+
+        mResolver.releaseUidsInRange(5, 9);     // Empty
+        verifyNoMoreInteractions(mListener);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index 2d760a7..88ca029 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.spy;
 
 import android.app.ActivityManagerInternal;
+import android.app.pinner.PinnedFileStat;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -47,22 +48,19 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.io.BufferedReader;
 import java.io.CharArrayWriter;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.io.StringReader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.Optional;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
@@ -138,6 +136,13 @@
             protected void publishBinderService(PinnerService service, Binder binderService) {
                 // Suppress this for testing, it's not needed and causes conflitcs.
             }
+
+            @Override
+            protected PinnerService.PinnedFile pinFileInternal(String fileToPin,
+                    int maxBytesToPin, boolean attemptPinIntrospection) {
+                return new PinnerService.PinnedFile(-1,
+                        maxBytesToPin, fileToPin, maxBytesToPin);
+            }
         };
     }
 
@@ -202,32 +207,38 @@
         return cw.toString();
     }
 
-    private int getPinnedSize(PinnerService pinnerService) throws Exception {
-        return getPinnedSizeImpl(pinnerService, "Total size: ");
+    private long getPinnedSize(PinnerService pinnerService) {
+        long totalBytesPinned = 0;
+        for (PinnedFileStat stat : pinnerService.getPinnerStats()) {
+            totalBytesPinned += stat.getBytesPinned();
+        }
+        return totalBytesPinned;
     }
 
-    private int getPinnedAnonSize(PinnerService pinnerService) throws Exception {
-        return getPinnedSizeImpl(pinnerService, "Pinned anon region: ");
+    private int getPinnedAnonSize(PinnerService pinnerService) {
+        List<PinnedFileStat> anonStats = pinnerService.getPinnerStats().stream()
+                .filter(pf -> pf.getGroupName().equals(PinnerService.ANON_REGION_STAT_NAME))
+                .toList();
+        int totalAnon = 0;
+        for (PinnedFileStat anonStat : anonStats) {
+            totalAnon += anonStat.getBytesPinned();
+        }
+        return totalAnon;
     }
 
-    private int getPinnedSizeImpl(PinnerService pinnerService, String sizeToken) throws Exception {
-        String dumpOutput = getPinnerServiceDump(pinnerService);
-        BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput));
-        Optional<Integer> size = bufReader.lines().filter(s -> s.contains(sizeToken))
-                .map(s -> Integer.valueOf(s.substring(sizeToken.length()))).findAny();
-        return size.orElse(-1);
+    private long getTotalPinnedFiles(PinnerService pinnerService) {
+        return pinnerService.getPinnerStats().stream().count();
     }
 
     private void setDeviceConfigPinnedAnonSize(long size) {
         mFakeDeviceConfigInterface.setProperty(
-                DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
                 "pin_shared_anon_size",
                 String.valueOf(size),
                 /*makeDefault=*/false);
     }
 
     @Test
-    @Ignore("b/309853498, pinning home app can fail with ENOMEM")
     public void testPinHomeApp() throws Exception {
         // Enable HOME app pinning
         mContext.getOrCreateTestableResources()
@@ -245,15 +256,13 @@
         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
         assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
 
-        // Check if dump() reports total pinned bytes
-        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
-        assertThat(totalPinnedSizeBytes).isGreaterThan(0);
+        assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
+        assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0);
 
         unpinAll(pinnerService);
     }
 
     @Test
-    @Ignore("b/309853498, pinning home app can fail with ENOMEM")
     public void testPinHomeAppOnBootCompleted() throws Exception {
         // Enable HOME app pinning
         mContext.getOrCreateTestableResources()
@@ -271,9 +280,7 @@
         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
         assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
 
-        // Check if dump() reports total pinned bytes
-        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
-        assertThat(totalPinnedSizeBytes).isGreaterThan(0);
+        assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
 
         unpinAll(pinnerService);
     }
@@ -294,12 +301,24 @@
         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
         assertThat(pinnedApps).isEmpty();
 
-        // Check if dump() reports total pinned bytes
-        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
+        long totalPinnedSizeBytes = getPinnedSize(pinnerService);
         assertThat(totalPinnedSizeBytes).isEqualTo(0);
 
         int pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService);
-        assertThat(pinnedAnonSizeBytes).isEqualTo(-1);
+        assertThat(pinnedAnonSizeBytes).isEqualTo(0);
+
+        unpinAll(pinnerService);
+    }
+
+    @Test
+    public void testPinFile() throws Exception {
+        PinnerService pinnerService = new PinnerService(mContext, mInjector);
+        pinnerService.onStart();
+
+        pinnerService.pinFile("test_file", 4096, null, "my_group");
+
+        assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
+        assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0);
 
         unpinAll(pinnerService);
     }
@@ -341,11 +360,8 @@
         waitForPinnerService(pinnerService);
         // An empty anon region should clear the associated status entry.
         pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService);
-        assertThat(pinnedAnonSizeBytes).isEqualTo(-1);
+        assertThat(pinnedAnonSizeBytes).isEqualTo(0);
 
         unpinAll(pinnerService);
     }
-
-    // TODO: Add test to check that the pages we expect to be pinned are actually pinned
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index a2e7cf3..3b39160 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -55,6 +55,10 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+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.provider.Settings;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
@@ -69,6 +73,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.testutils.TestHandler;
@@ -134,6 +139,9 @@
 @RunWith(AndroidJUnit4.class)
 public class FullScreenMagnificationGestureHandlerTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     public static final int STATE_IDLE = 1;
     public static final int STATE_ACTIVATED = 2;
     public static final int STATE_SHORTCUT_TRIGGERED = 3;
@@ -425,6 +433,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testDisablingTripleTap_removesInputLag() {
         mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
                 /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true);
@@ -436,6 +445,18 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() {
+        mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
+                /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true);
+        goFromStateIdleTo(STATE_IDLE);
+        allowEventDelegation();
+        tap();
+        // no fast forward
+        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
+    }
+
+    @Test
     public void testLongTapAfterShortcutTriggered_neverLogMagnificationTripleTap() {
         goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
 
@@ -510,6 +531,63 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() {
+        goFromStateIdleTo(STATE_IDLE);
+
+        twoFingerTap();
+        twoFingerTap();
+        twoFingerTap();
+
+        assertIn(STATE_ACTIVATED);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        reset(mMockMagnificationLogger);
+
+        twoFingerTap();
+        twoFingerTap();
+        twoFingerTap();
+
+        assertIn(STATE_IDLE);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
+        goFromStateIdleTo(STATE_IDLE);
+
+        twoFingerTap();
+        twoFingerTap();
+        twoFingerTapAndHold();
+
+        assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
+        goFromStateIdleTo(STATE_IDLE);
+
+        twoFingerTap();
+        twoFingerTap();
+        twoFingerSwipeAndHold();
+
+        assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
+    }
+
+    @Test
     public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
         // All delay motion events should be sent, if multi-tap with out of distance slop.
         // STATE_IDLE will check if tapCount() < 2.
@@ -1258,6 +1336,30 @@
         send(upEvent());
     }
 
+    private void twoFingerTap() {
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+        send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
+        send(upEvent());
+    }
+
+    private void twoFingerTapAndHold() {
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+        fastForward(2000);
+    }
+
+    private void twoFingerSwipeAndHold() {
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer1.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+    }
+
     private void triggerShortcut() {
         mMgh.notifyShortcutTriggered();
     }
diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
new file mode 100644
index 0000000..749b07d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
+import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.ILoudnessCodecUpdatesDispatcher;
+import android.media.LoudnessCodecInfo;
+import android.media.PlayerBase;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class LoudnessCodecHelperTest {
+    private static final String TAG = "LoudnessCodecHelperTest";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private LoudnessCodecHelper mLoudnessHelper;
+
+    @Mock
+    private AudioService mAudioService;
+    @Mock
+    private ILoudnessCodecUpdatesDispatcher.Default mDispatcher;
+
+    private final int mInitialApcPiid = 1;
+
+    @Before
+    public void setUp() throws Exception {
+        mLoudnessHelper = new LoudnessCodecHelper(mAudioService);
+
+        when(mAudioService.getActivePlaybackConfigurations()).thenReturn(
+                getApcListForPiids(mInitialApcPiid));
+
+        when(mDispatcher.asBinder()).thenReturn(Mockito.mock(IBinder.class));
+    }
+
+    @Test
+    public void registerDispatcher_sendsInitialUpdateOnStart() throws Exception {
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_4)));
+
+        verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any());
+    }
+
+    @Test
+    public void unregisterDispatcher_noInitialUpdateOnStart() throws Exception {
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+        mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/false,
+                        CODEC_METADATA_TYPE_MPEG_D)));
+
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    @Test
+    public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception {
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_4)));
+        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid,
+                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_D));
+
+        verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    @Test
+    public void addCodecInfoForUnstartedPiid_noUpdateSent() throws Exception {
+        final int newPiid = 2;
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_4)));
+        mLoudnessHelper.addLoudnessCodecInfo(newPiid,
+                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_D));
+
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    @Test
+    public void updateCodecParameters_updatesOnlyStartedPiids() throws Exception {
+        final int newPiid = 2;
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_4)));
+        //does not trigger dispatch since active apc list does not contain newPiid
+        mLoudnessHelper.startLoudnessCodecUpdates(newPiid,
+                List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_D)));
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+
+        // triggers dispatch for new active apc with newPiid
+        mLoudnessHelper.updateCodecParameters(getApcListForPiids(newPiid));
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(newPiid), any());
+    }
+
+    @Test
+    public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception {
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid,
+                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
+                        CODEC_METADATA_TYPE_MPEG_D));
+
+        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+
+        // no dispatch since mInitialApcPiid was not started
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    @Test
+    public void updateCodecParameters_removedCodecInfo_noDispatch() throws Exception {
+        final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111,
+                /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4);
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
+        mLoudnessHelper.removeLoudnessCodecInfo(mInitialApcPiid, info);
+
+        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+
+        // no second dispatch since codec info was removed for updates
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    @Test
+    public void updateCodecParameters_stoppedPiids_noDispatch() throws Exception {
+        final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111,
+                /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4);
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
+        mLoudnessHelper.stopLoudnessCodecUpdates(mInitialApcPiid);
+
+        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+
+        // no second dispatch since piid was removed for updates
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+                any());
+    }
+
+    private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) {
+        final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>();
+
+        AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS);
+        assumeTrue(devicesStatic.length > 0);
+        int index = new Random().nextInt(devicesStatic.length);
+        Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + index);
+        int deviceId = devicesStatic[index].getId();
+
+        for (int piid : piids) {
+            PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
+            AudioPlaybackConfiguration apc =
+                    new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/1);
+            apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
+
+            apcList.add(apc);
+        }
+        return apcList;
+    }
+
+    private static LoudnessCodecInfo getLoudnessInfo(int mediaCodecHash, boolean isDownmixing,
+            int metadataType) {
+        LoudnessCodecInfo info = new LoudnessCodecInfo();
+        info.isDownmixing = isDownmixing;
+        info.mediaCodecHashCode = mediaCodecHash;
+        info.metadataType = metadataType;
+
+        return info;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 0f3daec..74eb79d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -28,6 +28,8 @@
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -248,6 +250,45 @@
     }
 
     @Test
+    public void testOnErrorReceivedBeforeOnDialogAnimatedIn() throws RemoteException {
+        final int fingerprintId = 0;
+        final int faceId = 1;
+        setupFingerprint(fingerprintId, FingerprintSensorProperties.TYPE_REAR);
+        setupFace(faceId, true /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+        session.goToInitialState();
+
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            assertThat(sensor.getSensorState()).isEqualTo(BiometricSensor.STATE_WAITING_FOR_COOKIE);
+            session.onCookieReceived(
+                    session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+        }
+        assertThat(session.allCookiesReceived()).isTrue();
+        assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED);
+
+        final BiometricSensor faceSensor = session.mPreAuthInfo.eligibleSensors.get(faceId);
+        final BiometricSensor fingerprintSensor = session.mPreAuthInfo.eligibleSensors.get(
+                fingerprintId);
+        final int cookie = faceSensor.getCookie();
+        session.onErrorReceived(0, cookie, BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, 0);
+
+        assertThat(faceSensor.getSensorState()).isEqualTo(BiometricSensor.STATE_STOPPED);
+        assertThat(session.getState()).isEqualTo(STATE_ERROR_PENDING_SYSUI);
+
+        session.onDialogAnimatedIn(true);
+
+        assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED_UI_SHOWING);
+        assertThat(fingerprintSensor.getSensorState()).isEqualTo(
+                BiometricSensor.STATE_AUTHENTICATING);
+    }
+
+    @Test
     public void testCancelReducesAppetiteForCookies() throws Exception {
         setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
                 mock(IBiometricAuthenticator.class));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 7e6883b..ccbbaa5 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -100,7 +100,7 @@
     }
 
     @Test
-    public void registerInputDevice_deviceCreation_hasDeviceId() {
+    public void registerInputDevice_deviceCreation_hasDeviceId() throws Exception {
         final IBinder device1Token = new Binder("device1");
         mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token,
                 /* displayId= */ 1);
@@ -124,7 +124,7 @@
     }
 
     @Test
-    public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() {
+    public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() throws Exception {
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
@@ -137,7 +137,8 @@
     }
 
     @Test
-    public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() {
+    public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride()
+            throws Exception {
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
@@ -153,7 +154,7 @@
     }
 
     @Test
-    public void createNavigationTouchpad_hasDeviceId() {
+    public void createNavigationTouchpad_hasDeviceId() throws Exception {
         final IBinder deviceToken = new Binder();
         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
@@ -166,7 +167,7 @@
     }
 
     @Test
-    public void createNavigationTouchpad_setsTypeAssociation() {
+    public void createNavigationTouchpad_setsTypeAssociation() throws Exception {
         final IBinder deviceToken = new Binder();
         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
@@ -176,7 +177,7 @@
     }
 
     @Test
-    public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() {
+    public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() throws Exception {
         final IBinder deviceToken = new Binder();
         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
@@ -188,7 +189,7 @@
     }
 
     @Test
-    public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() {
+    public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() throws Exception {
         final IBinder deviceToken = new Binder("device");
 
         mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, deviceToken,
@@ -201,56 +202,7 @@
     }
 
     @Test
-    public void createInputDevice_tooLongNameRaisesException() {
-        final IBinder deviceToken = new Binder("device");
-        // The underlying uinput implementation only supports device names up to 80 bytes. This
-        // string is all ASCII characters, therefore if we have more than 80 ASCII characters we
-        // will have more than 80 bytes.
-        String deviceName =
-                "This.is.a.very.long.device.name.that.exceeds.the.maximum.length.of.80.bytes"
-                        + ".by.a.couple.bytes";
-
-        assertThrows(RuntimeException.class, () -> {
-            mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken,
-                    1);
-        });
-    }
-
-    @Test
-    public void createInputDevice_tooLongDeviceNameRaisesException() {
-        final IBinder deviceToken = new Binder("device");
-        // The underlying uinput implementation only supports device names up to 80 bytes (including
-        // a 0-byte terminator).
-        // This string is 79 characters and 80 bytes (including the 0-byte terminator)
-        String deviceName =
-                "This.is.a.very.long.device.name.that.exceeds.the.maximum.length01234567890123456";
-
-        assertThrows(RuntimeException.class, () -> {
-            mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken,
-                    1);
-        });
-    }
-
-    @Test
-    public void createInputDevice_stringWithLessThanMaxCharsButMoreThanMaxBytesRaisesException() {
-        final IBinder deviceToken = new Binder("device1");
-
-        // Has only 39 characters but is 109 bytes as utf-8
-        String device_name =
-                "░▄▄▄▄░\n" +
-                "▀▀▄██►\n" +
-                "▀▀███►\n" +
-                "░▀███►░█►\n" +
-                "▒▄████▀▀";
-
-        assertThrows(RuntimeException.class, () -> {
-            mInputController.createDpad(device_name, /*vendorId= */5, /*productId=*/5,
-                    deviceToken, 1);
-        });
-    }
-
-    @Test
-    public void createInputDevice_duplicateNamesAreNotAllowed() {
+    public void createInputDevice_duplicateNamesAreNotAllowed() throws Exception {
         final IBinder deviceToken1 = new Binder("deviceToken1");
         final IBinder deviceToken2 = new Binder("deviceToken2");
 
@@ -258,9 +210,9 @@
 
         mInputController.createDpad(sharedDeviceName, /*vendorId= */4, /*productId=*/4,
                 deviceToken1, 1);
-        assertThrows("Device names need to be unique", RuntimeException.class, () -> {
-            mInputController.createDpad(sharedDeviceName, /*vendorId= */5, /*productId=*/5,
-                    deviceToken2, 2);
-        });
+        assertThrows("Device names need to be unique",
+                InputController.DeviceCreationException.class,
+                () -> mInputController.createDpad(
+                        sharedDeviceName, /*vendorId= */5, /*productId=*/5, deviceToken2, 2));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 30300ec..9213601a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -368,6 +369,18 @@
                 new Handler(TestableLooper.get(this).getLooper()));
         when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
 
+        when(mNativeWrapperMock.writeButtonEvent(anyLong(), anyInt(), anyInt(), anyLong()))
+                .thenReturn(true);
+        when(mNativeWrapperMock.writeRelativeEvent(anyLong(), anyFloat(), anyFloat(), anyLong()))
+                .thenReturn(true);
+        when(mNativeWrapperMock.writeScrollEvent(anyLong(), anyFloat(), anyFloat(), anyLong()))
+                .thenReturn(true);
+        when(mNativeWrapperMock.writeKeyEvent(anyLong(), anyInt(), anyInt(), anyLong()))
+                .thenReturn(true);
+        when(mNativeWrapperMock.writeTouchEvent(anyLong(), anyInt(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong()))
+                .thenReturn(true);
+
         mInputManagerMockHelper = new InputManagerMockHelper(
                 TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
         // Allow virtual devices to be created on the looper thread for testing.
@@ -400,18 +413,24 @@
     public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -420,6 +439,8 @@
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
+        assertThat(mLocalService.getDeviceIdForDisplayId(DISPLAY_ID_1))
+                .isEqualTo(mDeviceImpl.getDeviceId());
     }
 
     @Test
@@ -760,6 +781,30 @@
     }
 
     @Test
+    public void getAllPersistentDeviceIds_respectsCurrentAssociations() {
+        mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+        TestableLooper.get(this).processAllMessages();
+
+        assertThat(mLocalService.getAllPersistentDeviceIds())
+                .containsExactly(mDeviceImpl.getPersistentDeviceId());
+
+        mVdms.onCdmAssociationsChanged(List.of(
+                createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING),
+                createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION),
+                createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH)));
+        TestableLooper.get(this).processAllMessages();
+
+        assertThat(mLocalService.getAllPersistentDeviceIds()).containsExactly(
+                VirtualDeviceImpl.createPersistentDeviceId(2),
+                VirtualDeviceImpl.createPersistentDeviceId(3));
+
+        mVdms.onCdmAssociationsChanged(Collections.emptyList());
+        TestableLooper.get(this).processAllMessages();
+
+        assertThat(mLocalService.getAllPersistentDeviceIds()).isEmpty();
+    }
+
+    @Test
     public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -1183,12 +1228,12 @@
 
     @Test
     public void sendKeyEvent_noFd() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
-                                .setKeyCode(KeyEvent.KEYCODE_A)
-                                .setAction(VirtualKeyEvent.ACTION_DOWN).build()));
+        assertThat(mDeviceImpl.sendKeyEvent(BINDER,
+                new VirtualKeyEvent.Builder()
+                        .setKeyCode(KeyEvent.KEYCODE_A)
+                        .setAction(VirtualKeyEvent.ACTION_DOWN)
+                        .build()))
+                .isFalse();
     }
 
     @Test
@@ -1201,24 +1246,24 @@
                 InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
 
-        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
-                .setKeyCode(keyCode)
-                .setAction(action)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendKeyEvent(BINDER,
+                new VirtualKeyEvent.Builder()
+                        .setKeyCode(keyCode)
+                        .setAction(action)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action, eventTimeNanos);
     }
 
     @Test
     public void sendButtonEvent_noFd() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        mDeviceImpl.sendButtonEvent(BINDER,
-                                new VirtualMouseButtonEvent.Builder()
-                                        .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK)
-                                        .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
-                                        .build()));
+        assertThat(mDeviceImpl.sendButtonEvent(BINDER,
+                new VirtualMouseButtonEvent.Builder()
+                        .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK)
+                        .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+                        .build()))
+                .isFalse();
     }
 
     @Test
@@ -1231,38 +1276,24 @@
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
         doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
-        mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
-                .setButtonCode(buttonCode)
-                .setAction(action)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendButtonEvent(BINDER,
+                new VirtualMouseButtonEvent.Builder()
+                        .setButtonCode(buttonCode)
+                        .setAction(action)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action, eventTimeNanos);
     }
 
     @Test
-    public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
-        final int fd = 1;
-        final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
-        final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
-        mInputController.addDeviceForTesting(BINDER, fd,
-                InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
-                INPUT_DEVICE_ID);
-        assertThrows(
-                IllegalStateException.class,
-                () ->
-                        mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
-                                .setButtonCode(buttonCode)
-                                .setAction(action).build()));
-    }
-
-    @Test
     public void sendRelativeEvent_noFd() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        mDeviceImpl.sendRelativeEvent(BINDER,
-                                new VirtualMouseRelativeEvent.Builder().setRelativeX(
-                                        0.0f).setRelativeY(0.0f).build()));
+        assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
+                new VirtualMouseRelativeEvent.Builder()
+                        .setRelativeX(0.0f)
+                        .setRelativeY(0.0f)
+                        .build()))
+                .isFalse();
     }
 
     @Test
@@ -1275,39 +1306,25 @@
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
         doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
-        mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
-                .setRelativeX(x)
-                .setRelativeY(y)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
+                new VirtualMouseRelativeEvent.Builder()
+                        .setRelativeX(x)
+                        .setRelativeY(y)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y, eventTimeNanos);
     }
 
-    @Test
-    public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
-        final int fd = 1;
-        final float x = -0.2f;
-        final float y = 0.7f;
-        mInputController.addDeviceForTesting(BINDER, fd,
-                InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
-                INPUT_DEVICE_ID);
-        assertThrows(
-                IllegalStateException.class,
-                () ->
-                        mDeviceImpl.sendRelativeEvent(BINDER,
-                                new VirtualMouseRelativeEvent.Builder()
-                                        .setRelativeX(x).setRelativeY(y).build()));
-    }
 
     @Test
     public void sendScrollEvent_noFd() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        mDeviceImpl.sendScrollEvent(BINDER,
-                                new VirtualMouseScrollEvent.Builder()
-                                        .setXAxisMovement(-1f)
-                                        .setYAxisMovement(1f).build()));
+        assertThat(mDeviceImpl.sendScrollEvent(BINDER,
+                new VirtualMouseScrollEvent.Builder()
+                        .setXAxisMovement(-1f)
+                        .setYAxisMovement(1f)
+                        .build()))
+                .isFalse();
     }
 
     @Test
@@ -1320,42 +1337,28 @@
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
         doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
-        mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
-                .setXAxisMovement(x)
-                .setYAxisMovement(y)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendScrollEvent(BINDER,
+                new VirtualMouseScrollEvent.Builder()
+                        .setXAxisMovement(x)
+                        .setYAxisMovement(y)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeScrollEvent(fd, x, y, eventTimeNanos);
     }
 
-    @Test
-    public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
-        final int fd = 1;
-        final float x = 0.5f;
-        final float y = 1f;
-        mInputController.addDeviceForTesting(BINDER, fd,
-                InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
-                INPUT_DEVICE_ID);
-        assertThrows(
-                IllegalStateException.class,
-                () ->
-                        mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
-                                .setXAxisMovement(x)
-                                .setYAxisMovement(y).build()));
-    }
 
     @Test
     public void sendTouchEvent_noFd() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
-                                .setX(0.0f)
-                                .setY(0.0f)
-                                .setAction(VirtualTouchEvent.ACTION_UP)
-                                .setPointerId(1)
-                                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
-                                .build()));
+        assertThat(mDeviceImpl.sendTouchEvent(BINDER,
+                new VirtualTouchEvent.Builder()
+                        .setX(0.0f)
+                        .setY(0.0f)
+                        .setAction(VirtualTouchEvent.ACTION_UP)
+                        .setPointerId(1)
+                        .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                        .build()))
+                .isFalse();
     }
 
     @Test
@@ -1370,14 +1373,16 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
-        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
-                .setX(x)
-                .setY(y)
-                .setAction(action)
-                .setPointerId(pointerId)
-                .setToolType(toolType)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendTouchEvent(BINDER,
+                new VirtualTouchEvent.Builder()
+                        .setX(x)
+                        .setY(y)
+                        .setAction(action)
+                        .setPointerId(pointerId)
+                        .setToolType(toolType)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
                 Float.NaN, eventTimeNanos);
     }
@@ -1396,16 +1401,18 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
-        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
-                .setX(x)
-                .setY(y)
-                .setAction(action)
-                .setPointerId(pointerId)
-                .setToolType(toolType)
-                .setPressure(pressure)
-                .setMajorAxisSize(majorAxisSize)
-                .setEventTimeNanos(eventTimeNanos)
-                .build());
+        assertThat(mDeviceImpl.sendTouchEvent(BINDER,
+                new VirtualTouchEvent.Builder()
+                        .setX(x)
+                        .setY(y)
+                        .setAction(action)
+                        .setPointerId(pointerId)
+                        .setToolType(toolType)
+                        .setPressure(pressure)
+                        .setMajorAxisSize(majorAxisSize)
+                        .setEventTimeNanos(eventTimeNanos)
+                        .build()))
+                .isTrue();
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
                 majorAxisSize, eventTimeNanos);
     }
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 2583023..01922e0 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
@@ -57,12 +57,11 @@
     private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
     private static final int CAMERA_WIDTH_1 = 100;
     private static final int CAMERA_HEIGHT_1 = 200;
-    private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565;
 
     private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
     private static final int CAMERA_WIDTH_2 = 400;
     private static final int CAMERA_HEIGHT_2 = 600;
-    private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2;
+    private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
 
     @Mock
     private IVirtualCameraService mVirtualCameraServiceMock;
@@ -81,7 +80,7 @@
     @Test
     public void registerCamera_registersCamera() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
 
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -89,13 +88,13 @@
         VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
         assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
         assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
-                CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+                CAMERA_HEIGHT_1, CAMERA_FORMAT);
     }
 
     @Test
     public void unregisterCamera_unregistersCamera() throws Exception {
         VirtualCameraConfig config = createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1);
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
         mVirtualCameraController.unregisterCamera(config);
 
         verify(mVirtualCameraServiceMock).unregisterCamera(any());
@@ -104,9 +103,9 @@
     @Test
     public void close_unregistersAllCameras() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2));
+                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
 
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -117,9 +116,9 @@
                 configurationCaptor.getAllValues();
         assertThat(virtualCameraConfigurations).hasSize(2);
         assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1,
-                CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+                CAMERA_HEIGHT_1, CAMERA_FORMAT);
         assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2,
-                CAMERA_HEIGHT_2, CAMERA_FORMAT_2);
+                CAMERA_HEIGHT_2, CAMERA_FORMAT);
     }
 
     private VirtualCameraConfig createVirtualCameraConfig(
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 2db46e6..46ead85 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -571,6 +571,29 @@
     }
 
     @Test
+    public void testDebugTagsPersisted() throws Exception {
+        JobInfo ji = new Builder(53, mComponent)
+                .setPersisted(true)
+                .addDebugTag("a")
+                .addDebugTag("b")
+                .addDebugTag("c")
+                .addDebugTag("d")
+                .removeDebugTag("d")
+                .build();
+        final JobStatus js = JobStatus.createFromJobInfo(ji, SOME_UID, null, -1, null, null);
+        mTaskStoreUnderTest.add(js);
+        waitForPendingIo();
+
+        Set<String> expectedTags = Set.of("a", "b", "c");
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Debug tags not correctly persisted",
+                expectedTags, loaded.getJob().getDebugTags());
+    }
+
+    @Test
     public void testNamespacePersisted() throws Exception {
         final String namespace = "my.test.namespace";
         JobInfo.Builder b = new Builder(93, mComponent)
@@ -675,6 +698,22 @@
     }
 
     @Test
+    public void testTraceTagPersisted() throws Exception {
+        JobInfo ji = new Builder(53, mComponent)
+                .setPersisted(true)
+                .setTraceTag("tag")
+                .build();
+        final JobStatus js = JobStatus.createFromJobInfo(ji, SOME_UID, null, -1, null, null);
+        mTaskStoreUnderTest.add(js);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Trace tag not correctly persisted", "tag", loaded.getJob().getTraceTag());
+    }
+
+    @Test
     public void testEstimatedNetworkBytes() throws Exception {
         assertPersistedEquals(new JobInfo.Builder(0, mComponent)
                 .setPersisted(true)
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
index 1e73a45..5aef7a3 100644
--- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -90,7 +90,7 @@
 
     @Test
     public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
 
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
@@ -105,7 +105,7 @@
         audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
         callAudioRoutesObserver(audioRoutesInfo);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
     }
@@ -117,7 +117,7 @@
 
         mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
     }
@@ -135,7 +135,7 @@
 
         mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
     }
@@ -155,7 +155,7 @@
 
         mController.selectRoute(null);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
     }
@@ -171,7 +171,7 @@
 
         mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
     }
@@ -202,7 +202,7 @@
 
         mController.updateVolume(VOLUME_SAMPLE_1);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
         assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
     }
@@ -222,7 +222,7 @@
 
         mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
 
-        MediaRoute2Info route2Info = mController.getDeviceRoute();
+        MediaRoute2Info route2Info = mController.getSelectedRoute();
         assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
         assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
     }
diff --git a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
index 24e4851..aed68a5 100644
--- a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
@@ -104,7 +104,7 @@
                     mOnDeviceRouteChangedListener
             );
 
-            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
 
             assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
             assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -129,7 +129,7 @@
                     mOnDeviceRouteChangedListener
             );
 
-            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
 
             assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
             assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -243,7 +243,7 @@
                     mOnDeviceRouteChangedListener
             );
 
-            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute();
 
             assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType);
             assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue))
@@ -310,7 +310,7 @@
             // Simulating wired device being connected.
             callAudioRoutesObserver(audioRoutesInfo);
 
-            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
 
             assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
             assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME))
@@ -324,7 +324,7 @@
             AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
             callAudioRoutesObserver(fakeBluetoothAudioRoute);
 
-            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
 
             assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
             assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
@@ -334,12 +334,12 @@
 
         @Test
         public void updateVolume_differentValue_updatesDeviceRouteVolume() {
-            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute();
             assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
 
             assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
 
-            actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            actualMediaRoute = mDeviceRouteController.getSelectedRoute();
             assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
index 949f8e7..0e881ef 100644
--- a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
@@ -221,7 +221,7 @@
         callCallbacksForNetworkConnect(defaultCallback, mNetwork);
 
         // Vpn is starting
-        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP);
+        verify(mVpn).startLegacyVpnPrivileged(mProfile);
         verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
                 argThat(notification -> isExpectedNotification(notification,
                         R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
@@ -242,7 +242,7 @@
         // LockdownVpnTracker#handleStateChangedLocked. This is a bug.
         // TODO: consider fixing this.
         verify(mVpn, never()).stopVpnRunnerPrivileged();
-        verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any());
+        verify(mVpn, never()).startLegacyVpnPrivileged(any());
         verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
     }
 
@@ -302,7 +302,7 @@
 
         // Vpn is restarted.
         verify(mVpn).stopVpnRunnerPrivileged();
-        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp);
+        verify(mVpn).startLegacyVpnPrivileged(mProfile);
         verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
         verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
                 argThat(notification -> isExpectedNotification(notification,
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index 3a3ab84..dd687fd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -27,9 +27,9 @@
 import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.PackageState;
 
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
index bfd4072..186a27c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
@@ -45,6 +45,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.pm.UserInfo;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -135,6 +136,53 @@
     }
 
     @Test
+    public void testCreatePrivateProfileUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session =
+                mUserJourneyLogger.logUserJourneyBegin(-1, USER_JOURNEY_USER_CREATE);
+
+        report1.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                -1,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_BEGIN,
+                ERROR_CODE_UNSPECIFIED,
+                1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final int profileUserId = 10;
+        UserInfo targetUser =
+                new UserInfo(
+                        profileUserId,
+                        "test private target user",
+                        /* iconPath= */ null,
+                        UserInfo.FLAG_PROFILE,
+                        UserManager.USER_TYPE_PROFILE_PRIVATE);
+        mUserJourneyLogger.logUserCreateJourneyFinish(0, targetUser);
+
+        report1.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                profileUserId,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_FINISH,
+                ERROR_CODE_UNSPECIFIED,
+                2);
+
+        report2.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                USER_JOURNEY_USER_CREATE,
+                0,
+                profileUserId,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_PRIVATE,
+                UserInfo.FLAG_PROFILE,
+                ERROR_CODE_UNSPECIFIED,
+                1);
+    }
+
+    @Test
     public void testRemoveUserJourney() {
         final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
         final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
@@ -161,6 +209,54 @@
     }
 
     @Test
+    public void testRemovePrivateProfileUserJourneyWithError() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final int profileUserId = 10;
+        final UserJourneyLogger.UserJourneySession session =
+                mUserJourneyLogger.logUserJourneyBegin(profileUserId, USER_JOURNEY_USER_REMOVE);
+
+        report1.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                profileUserId,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_BEGIN,
+                ERROR_CODE_UNSPECIFIED,
+                1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser =
+                new UserInfo(
+                        profileUserId,
+                        "test private target user",
+                        /* iconPath= */ null,
+                        UserInfo.FLAG_PROFILE,
+                        UserManager.USER_TYPE_PROFILE_PRIVATE);
+        mUserJourneyLogger.logUserJourneyFinishWithError(
+                0, targetUser, USER_JOURNEY_USER_REMOVE, ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+
+        report1.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                profileUserId,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_ERROR,
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
+                2);
+
+        report2.captureAndAssert(
+                mUserJourneyLogger,
+                session.mSessionId,
+                USER_JOURNEY_USER_REMOVE,
+                0,
+                profileUserId,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_PRIVATE,
+                UserInfo.FLAG_PROFILE,
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
+                1);
+    }
+
+    @Test
     public void testStartUserJourney() {
         final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
         final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 57b1225..d70a4fd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -60,7 +60,8 @@
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
                 .setShowInSettings(45)
-                .setHideInSettingsInQuietMode(false)
+                .setShowInSharingSurfaces(78)
+                .setShowInQuietMode(12)
                 .setInheritDevicePolicy(67)
                 .setUseParentsContacts(false)
                 .setCrossProfileIntentFilterAccessControl(10)
@@ -74,7 +75,8 @@
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
         actualProps.setShowInSettings(32);
-        actualProps.setHideInSettingsInQuietMode(true);
+        actualProps.setShowInSharingSurfaces(46);
+        actualProps.setShowInQuietMode(27);
         actualProps.setInheritDevicePolicy(51);
         actualProps.setUseParentsContacts(true);
         actualProps.setCrossProfileIntentFilterAccessControl(20);
@@ -236,8 +238,10 @@
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
         assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
-        assertThat(expected.getHideInSettingsInQuietMode())
-                .isEqualTo(actual.getHideInSettingsInQuietMode());
+        assertThat(expected.getShowInSharingSurfaces()).isEqualTo(
+                actual.getShowInSharingSurfaces());
+        assertThat(expected.getShowInQuietMode())
+                .isEqualTo(actual.getShowInQuietMode());
         assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
         assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
         assertThat(expected.getCrossProfileIntentFilterAccessControl())
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 48eb5c6..77f6939 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -29,6 +29,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
@@ -91,7 +92,8 @@
                 .setCredentialShareableWithParent(false)
                 .setAuthAlwaysRequiredToDisableQuietMode(true)
                 .setShowInSettings(900)
-                .setHideInSettingsInQuietMode(true)
+                .setShowInSharingSurfaces(20)
+                .setShowInQuietMode(30)
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(true);
@@ -107,9 +109,9 @@
                 .setIconBadge(28)
                 .setBadgePlain(29)
                 .setBadgeNoBackground(30)
-                .setLabel(31)
                 .setMaxAllowedPerParent(32)
                 .setStatusBarIcon(33)
+                .setLabels(34, 35, 36)
                 .setDefaultRestrictions(restrictions)
                 .setDefaultSystemSettings(systemSettings)
                 .setDefaultSecureSettings(secureSettings)
@@ -124,9 +126,11 @@
         assertEquals(28, type.getIconBadge());
         assertEquals(29, type.getBadgePlain());
         assertEquals(30, type.getBadgeNoBackground());
-        assertEquals(31, type.getLabel());
         assertEquals(32, type.getMaxAllowedPerParent());
         assertEquals(33, type.getStatusBarIcon());
+        assertEquals(34, type.getLabel(0));
+        assertEquals(35, type.getLabel(1));
+        assertEquals(36, type.getLabel(2));
 
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
         assertNotSame(restrictions, type.getDefaultRestrictions());
@@ -164,7 +168,9 @@
         assertTrue(type.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
-        assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
+        assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
+        assertEquals(30,
+                type.getDefaultUserPropertiesReference().getShowInQuietMode());
         assertEquals(340, type.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent());
@@ -203,7 +209,7 @@
         assertEquals(Resources.ID_NULL, type.getStatusBarIcon());
         assertEquals(Resources.ID_NULL, type.getBadgeLabel(0));
         assertEquals(Resources.ID_NULL, type.getBadgeColor(0));
-        assertEquals(Resources.ID_NULL, type.getLabel());
+        assertEquals(Resources.ID_NULL, type.getLabel(0));
         assertTrue(type.getDefaultRestrictions().isEmpty());
         assertTrue(type.getDefaultSystemSettings().isEmpty());
         assertTrue(type.getDefaultSecureSettings().isEmpty());
@@ -222,7 +228,9 @@
         assertFalse(props.isCredentialShareableWithParent());
         assertFalse(props.getDeleteAppWithParent());
         assertFalse(props.getAlwaysVisible());
-        assertFalse(props.getHideInSettingsInQuietMode());
+        assertEquals(UserProperties.SHOW_IN_LAUNCHER_SEPARATE, props.getShowInSharingSurfaces());
+        assertEquals(UserProperties.SHOW_IN_QUIET_MODE_PAUSED,
+                props.getShowInQuietMode());
 
         assertFalse(type.hasBadge());
     }
@@ -311,8 +319,9 @@
                 .setCredentialShareableWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setShowInSettings(20)
-                .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(21)
+                .setShowInSharingSurfaces(22)
+                .setShowInQuietMode(24)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(false);
 
@@ -354,9 +363,11 @@
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
-        assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
+        assertEquals(22, aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
+        assertEquals(24,
+                aospType.getDefaultUserPropertiesReference().getShowInQuietMode());
         assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
 
@@ -403,7 +414,10 @@
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
-        assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
+        assertEquals(22,
+                aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
+        assertEquals(24,
+                aospType.getDefaultUserPropertiesReference().getShowInQuietMode());
         assertEquals(450, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 2b6d8ed..c7d80ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -343,6 +343,8 @@
         assertThat(mainUserId).isEqualTo(parentProfileInfo.id);
         removeUser(userInfo.id);
         assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
+        assertThat(mUserManager.getProfileLabel()).isEqualTo(
+                Resources.getSystem().getString(userTypeDetails.getLabel(0)));
     }
 
     @MediumTest
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index a8e3c7e..8464969 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -53,10 +53,10 @@
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiDevice;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.After;
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 b2843d8..1bfd43f 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,10 +37,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Assert;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index ad9f920..0f87202 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -29,10 +29,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import dalvik.system.DelegateLastClassLoader;
 import dalvik.system.DexClassLoader;
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 cf2b748..ee23a00 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
@@ -492,6 +492,40 @@
         validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
     }
 
+    @Test
+    fun totalMetadataValuesExceedMax_shouldFail() {
+        val value = "x".repeat(8192)
+        var tags = ""
+        repeat(32) { index ->
+            tags += "<meta-data name=\"name$index\" value=\"$value\" />"
+        }
+        var xml = "<application>$tags</application>"
+        try {
+            parseXmlForMetadata(xml)
+        } catch (e: SecurityException) {
+            fail(
+                "Failed to parse component meta-data when values have not exceeded max allowed"
+            )
+        }
+        try {
+            parseXmlForMetadataRes(xml)
+        } catch (e: SecurityException) {
+            fail(
+                "Failed to parse component meta-data when values have not exceeded max allowed"
+            )
+        }
+        tags += "<meta-data name=\"last\" value=\"x\" />"
+        xml = "<application>$tags</application>"
+        var e = assertThrows(SecurityException::class.java) {
+            parseXmlForMetadata(xml)
+        }
+        assertEquals("Max total meta data size limit exceeded for application", e.message)
+        e = assertThrows(SecurityException::class.java) {
+            parseXmlForMetadataRes(xml)
+        }
+        assertEquals("Max total meta data size limit exceeded for application", e.message)
+    }
+
     private fun validateTagAttrComponentName(tag: String, attr: String, index: Int) {
         val passNames = arrayOf("com.android.TestClass", "TestClass", "_", "$", ".TestClass", "上")
         for (name in passNames) {
@@ -664,6 +698,38 @@
         } while (type != XmlPullParser.END_DOCUMENT)
     }
 
+    fun parseXmlForMetadata(manifestStr: String) {
+        pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
+        val validator = Validator()
+        do {
+            val type = pullParser.next()
+            validator.validate(pullParser)
+            if (type == XmlPullParser.START_TAG && pullParser.getName().equals("meta-data")) {
+                val name = "value"
+                val value = pullParser.getAttributeValue("", name)
+                validator.validateStrAttr(pullParser, "value", value)
+            }
+        } while (type != XmlPullParser.END_DOCUMENT)
+    }
+
+    fun parseXmlForMetadataRes(manifestStr: String) {
+        pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
+        val validator = Validator()
+        do {
+            val type = pullParser.next()
+            validator.validate(pullParser)
+            if (type == XmlPullParser.START_TAG && pullParser.getName().equals("meta-data")) {
+                val name = "value"
+                val value = pullParser.getAttributeValue("", name)
+                validator.validateResStrAttr(
+                    pullParser,
+                    R.styleable.AndroidManifestMetaData_value,
+                    value
+                )
+            }
+        } while (type != XmlPullParser.END_DOCUMENT)
+    }
+
     fun expectedCountErrorMsg(tag: String, parentTag: String) =
             "The number of child $tag elements exceeded the max allowed in $parentTag"
 
diff --git a/services/tests/servicestests/src/com/android/server/power/OWNERS b/services/tests/servicestests/src/com/android/server/power/OWNERS
index ef4c0bf..fe93ebb 100644
--- a/services/tests/servicestests/src/com/android/server/power/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/power/OWNERS
@@ -1,3 +1,3 @@
 include /services/core/java/com/android/server/power/OWNERS
 
-per-file ThermalManagerServiceTest.java=wvw@google.com, xwxw@google.com
\ No newline at end of file
+per-file ThermalManagerServiceTest.java=file:/THERMAL_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 3748527..d09aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -44,7 +44,6 @@
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
 import android.os.Process;
-import android.os.WorkDuration;
 import android.util.Log;
 
 import com.android.server.FgThread;
@@ -90,11 +89,6 @@
     private static final long[] DURATIONS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
-    private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
-        new WorkDuration(1L, 11L, 8L, 4L, 1L),
-        new WorkDuration(2L, 13L, 8L, 6L, 2L),
-        new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
-    };
 
     @Mock private Context mContext;
     @Mock private HintManagerService.NativeWrapper mNativeWrapperMock;
@@ -599,55 +593,4 @@
         }
         a.close();
     }
-
-    @Test
-    public void testReportActualWorkDuration2() throws Exception {
-        HintManagerService service = createService();
-        IBinder token = new Binder();
-
-        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
-                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
-
-        a.updateTargetWorkDuration(100L);
-        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
-        verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
-                eq(WORK_DURATIONS_THREE));
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(new WorkDuration[] {});
-        });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)});
-        });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)});
-        });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
-        });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(
-                    new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)});
-        });
-
-        reset(mNativeWrapperMock);
-        // Set session to background, then the duration would not be updated.
-        service.mUidObserver.onUidStateChanged(
-                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
-
-        // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
-        final CountDownLatch latch = new CountDownLatch(1);
-        FgThread.getHandler().post(() -> {
-            latch.countDown();
-        });
-        latch.await();
-
-        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
-        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
-        verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 6ae2658..cd29c80 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -24,11 +24,13 @@
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import android.app.usage.Flags;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 import android.util.LongSparseArray;
@@ -183,6 +185,17 @@
                 case Event.LOCUS_ID_SET:
                     event.mLocusId = "locus" + (i % 7); //"random" locus
                     break;
+                case Event.USER_INTERACTION:
+                    if (Flags.userInteractionTypeApi()) {
+                        // "random" user interaction extras.
+                        PersistableBundle extras = new PersistableBundle();
+                        extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                                "fake.namespace.category" + (i % 13));
+                        extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION,
+                                "fakeaction" + (i % 13));
+                        event.mExtras = extras;
+                    }
+                    break;
             }
 
             mIntervalStats.addEvent(event);
@@ -295,6 +308,18 @@
                         assertEquals(e1.mLocusIdToken, e2.mLocusIdToken,
                                 "Usage event " + debugId);
                         break;
+                    case Event.USER_INTERACTION:
+                        if (Flags.userInteractionTypeApi()) {
+                            PersistableBundle extras1 = e1.getExtras();
+                            PersistableBundle extras2 = e2.getExtras();
+                            assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                                    extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                                    "Usage event " + debugId);
+                            assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                                    extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                                    "Usage event " + debugId);
+                        }
+                        break;
                 }
                 // fallthrough
             case 4: // test fields added in version 4
diff --git a/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java
new file mode 100644
index 0000000..377e4c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.provider.DeviceConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link UserSettingDeviceConfigMediator}
+ */
+@RunWith(AndroidJUnit4.class)
+public class UserSettingDeviceConfigMediatorTest {
+    @Test
+    public void testDeviceConfigOnly() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 1)
+                .setFloat("float", .5f)
+                .setBoolean("boolean", true)
+                .setLong("long", 123456789)
+                .setString("string", "abc123")
+                .build();
+
+        mediator.setDeviceConfigProperties(properties);
+
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("abc123", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+
+        // Clear the properties
+        mediator.setDeviceConfigProperties(null);
+
+        assertEquals(123, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.8f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(987654321, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+    }
+
+    @Test
+    public void testSettingsOnly() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123";
+
+        mediator.setSettingsString(settings);
+
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("abc123", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+
+        // Clear the settings
+        mediator.setSettingsString(null);
+
+        assertEquals(123, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.8f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(987654321, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+    }
+
+    @Test
+    public void testSettingsOverridesAll() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123,"
+                + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true,"
+                + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 10)
+                .setInt("intOnlyInDeviceConfig", 9001)
+                .setFloat("float", .7f)
+                .setFloat("floatOnlyInDeviceConfig", .9f)
+                .setBoolean("boolean", false)
+                .setBoolean("booleanOnlyInDeviceConfig", true)
+                .setLong("long", 60000001)
+                .setLong("longOnlyInDeviceConfig", 7357)
+                .setString("string", "xyz987")
+                .setString("stringOnlyInDeviceConfig", "deviceConfigString")
+                .build();
+
+        mediator.setSettingsString(settings);
+        mediator.setDeviceConfigProperties(properties);
+
+        // Since settings overrides all, anything in DeviceConfig should be ignored,
+        // even if settings doesn't have a value for it.
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(9, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(123, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("abc123", mediator.getString("string", "default"));
+        assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInDeviceConfig", "default"));
+
+        // Nothing in settings, do DeviceConfig can be used.
+        mediator.setSettingsString("");
+
+        assertEquals(10, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.7f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(60000001, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+
+        // Nothing in settings, do DeviceConfig can be used.
+        mediator.setSettingsString(null);
+
+        assertEquals(10, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.7f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(60000001, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+    }
+
+    @Test
+    public void testSettingsOverridesIndividual() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123,"
+                + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true,"
+                + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 10)
+                .setInt("intOnlyInDeviceConfig", 9001)
+                .setFloat("float", .7f)
+                .setFloat("floatOnlyInDeviceConfig", .9f)
+                .setBoolean("boolean", false)
+                .setBoolean("booleanOnlyInDeviceConfig", true)
+                .setLong("long", 60000001)
+                .setLong("longOnlyInDeviceConfig", 7357)
+                .setString("string", "xyz987")
+                .setString("stringOnlyInDeviceConfig", "deviceConfigString")
+                .build();
+
+        mediator.setSettingsString(settings);
+        mediator.setDeviceConfigProperties(properties);
+
+        // Since settings overrides individual, anything in DeviceConfig that doesn't exist in
+        // settings should be used.
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(9, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("abc123", mediator.getString("string", "default"));
+        assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index ebe45a6..32082e3 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -25,6 +27,10 @@
 import android.content.pm.Signature;
 import android.os.Build;
 import android.os.Bundle;
+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.test.suitebuilder.annotation.MediumTest;
 import android.util.Base64;
 import android.webkit.UserPackage;
@@ -35,6 +41,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -55,11 +62,14 @@
 public class WebViewUpdateServiceTest {
     private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName();
 
-    private WebViewUpdateServiceImpl mWebViewUpdateServiceImpl;
+    private WebViewUpdateServiceInterface mWebViewUpdateServiceImpl;
     private TestSystemImpl mTestSystemImpl;
 
     private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * Creates a new instance.
      */
@@ -92,8 +102,13 @@
         TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable,
                 multiProcessDefault);
         mTestSystemImpl = Mockito.spy(testing);
-        mWebViewUpdateServiceImpl =
-            new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
+        if (updateServiceV2()) {
+            mWebViewUpdateServiceImpl =
+                    new WebViewUpdateServiceImpl2(null /*Context*/, mTestSystemImpl);
+        } else {
+            mWebViewUpdateServiceImpl =
+                    new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
+        }
     }
 
     private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
@@ -1310,11 +1325,13 @@
     }
 
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
     public void testMultiProcessEnabledByDefault() {
         testMultiProcessByDefault(true /* enabledByDefault */);
     }
 
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
     public void testMultiProcessDisabledByDefault() {
         testMultiProcessByDefault(false /* enabledByDefault */);
     }
@@ -1344,6 +1361,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
     public void testMultiProcessEnabledByDefaultWithSettingsValue() {
         testMultiProcessByDefaultWithSettingsValue(
                 true /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
@@ -1356,6 +1374,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
     public void testMultiProcessDisabledByDefaultWithSettingsValue() {
         testMultiProcessByDefaultWithSettingsValue(
                 false /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
@@ -1431,4 +1450,21 @@
         checkPreparationPhasesForPackage(currentSdkPackage.packageName,
                 1 /* first preparation phase */);
     }
+
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
+        String nonDefaultPackage = "nonDefaultPackage";
+        String defaultPackage1 = "defaultPackage1";
+        String defaultPackage2 = "defaultPackage2";
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(nonDefaultPackage, "", false, false, null),
+                    new WebViewProviderInfo(defaultPackage1, "", true, false, null),
+                    new WebViewProviderInfo(defaultPackage2, "", true, false, null)
+                };
+        setupWithPackages(packages);
+        assertEquals(
+                defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName);
+    }
 }
diff --git a/services/tests/timetests/TEST_MAPPING b/services/tests/timetests/TEST_MAPPING
index b24010c..e549229 100644
--- a/services/tests/timetests/TEST_MAPPING
+++ b/services/tests/timetests/TEST_MAPPING
@@ -1,6 +1,5 @@
 {
-  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksTimeServicesTests"
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
index 4b6183d..8bc027d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
@@ -31,6 +31,7 @@
 import android.app.Notification;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -39,6 +40,7 @@
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -55,6 +57,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ArchiveTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int SIZE = 5;
 
     private NotificationManagerService.Archive mArchive;
@@ -249,4 +254,29 @@
             assertThat(expected).contains(sbn.getKey());
         }
     }
+
+    @Test
+    public void testRemoveNotificationsByPackage() {
+        List<String> expected = new ArrayList<>();
+
+        StatusBarNotification sbn_remove = getNotification("pkg_remove", 0,
+                UserHandle.of(USER_CURRENT));
+        mArchive.record(sbn_remove, REASON_CANCEL);
+
+        StatusBarNotification sbn_keep = getNotification("pkg_keep", 1,
+                UserHandle.of(USER_CURRENT));
+        mArchive.record(sbn_keep, REASON_CANCEL);
+        expected.add(sbn_keep.getKey());
+
+        StatusBarNotification sbn_remove2 = getNotification("pkg_remove", 2,
+                UserHandle.of(USER_CURRENT));
+        mArchive.record(sbn_remove2, REASON_CANCEL);
+
+        mArchive.removePackageNotifications("pkg_remove", USER_CURRENT);
+        List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true));
+        assertThat(actual).hasSize(expected.size());
+        for (StatusBarNotification sbn : actual) {
+            assertThat(expected).contains(sbn.getKey());
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 2136811..344a4b0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1077,6 +1077,26 @@
     }
 
     @Test
+    public void testPackageUninstall_componentNoLongerUserSetList() throws Exception {
+        final String pkg = "this.is.a.package.name";
+        final String component = pkg + "/Ba";
+        for (int approvalLevel : new int[] { APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            writeExpectedValuesToSettings(approvalLevel);
+            service.migrateToXml();
+
+            final String verifyValue = (approvalLevel == APPROVAL_BY_COMPONENT) ? component : pkg;
+
+            assertThat(service.isPackageOrComponentAllowed(verifyValue, 0)).isTrue();
+            assertThat(service.isPackageOrComponentUserSet(verifyValue, 0)).isTrue();
+
+            service.onPackagesChanged(true, new String[]{pkg}, new int[]{103});
+            assertThat(service.isPackageOrComponentUserSet(verifyValue, 0)).isFalse();
+        }
+    }
+
+    @Test
     public void testIsPackageAllowed() {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
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 7fb8b30..09ffe71 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -65,6 +65,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -72,7 +73,6 @@
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
@@ -87,8 +87,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -243,7 +241,6 @@
 
 import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
@@ -309,7 +306,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -739,9 +735,6 @@
         clearInvocations(mRankingHandler);
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
 
-        mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
-        mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
-
         var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class);
         mService.permissionChecker = checker;
         when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean()))
@@ -818,6 +811,20 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
+    private void simulatePackageRemovedBroadcast(String pkg, int uid) {
+        // mimics receive broadcast that package is removed, but doesn't remove the package.
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                new String[]{pkg});
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+
+        final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.setData(Uri.parse("package:" + pkg));
+        intent.putExtras(extras);
+
+        mPackageIntentReceiver.onReceive(getContext(), intent);
+    }
+
     private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) {
         // mimics receive broadcast that package is (un)distracting
         // but does not actually register that info with packagemanager
@@ -883,6 +890,22 @@
         mTestNotificationChannel.setAllowBubbles(channelEnabled);
     }
 
+    private void setUpPrefsForHistory(int uid, boolean globalEnabled) {
+        // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
+        // Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0);
+
+        // Forces an update by calling observe on mSettingsObserver, which picks up the settings
+        // changes above.
+        mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
+
+        assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0);
+    }
+
     private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
         Notification.Builder nb = new Notification.Builder(mContext, "a")
                 .setContentTitle("foo")
@@ -5272,7 +5295,7 @@
                 new Intent(Intent.ACTION_LOCALE_CHANGED));
 
         verify(mZenModeHelper, times(1)).updateDefaultZenRules(
-                anyInt(), anyBoolean());
+                anyInt());
     }
 
     @Test
@@ -8728,7 +8751,7 @@
 
         // verify that zen mode helper gets passed in a package name of "android"
         verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(), eq(true)); // system call counts as "is system or system ui"
+                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call
     }
 
     @Test
@@ -8750,7 +8773,7 @@
 
         // verify that zen mode helper gets passed in a package name of "android"
         verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(),  eq(true));  // system call counts as "system or system ui"
+                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI));  // system call
     }
 
     @Test
@@ -8771,7 +8794,7 @@
         // verify that zen mode helper gets passed in the package name from the arg, not the owner
         verify(mockZenModeHelper).addAutomaticZenRule(
                 eq("another.package"), eq(rule), anyString(), anyInt(),
-                eq(false));  // doesn't count as a system/systemui call
+                eq(ZenModeHelper.FROM_APP));  // doesn't count as a system/systemui call
     }
 
     @Test
@@ -9836,6 +9859,43 @@
     }
 
     @Test
+    public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException {
+        // Enables Notification History setting
+        setUpPrefsForHistory(mUid, true /* =enabled */);
+
+        // Posts a notification to the mTestNotificationChannel.
+        final NotificationRecord notif = generateNotificationRecord(
+                mTestNotificationChannel, 1, null, false);
+        mService.addNotification(notif);
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(
+                notif.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+
+        // Cancels all notifications.
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+                notif.getUserId(), REASON_CANCEL);
+        waitForIdle();
+        notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+
+        // Checks that notification history's recently canceled archive contains the notification.
+        notifs = mBinderService.getHistoricalNotificationsWithAttribution(PKG,
+                        mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */);
+        waitForIdle();
+        assertEquals(1, notifs.length);
+
+        // Remove sthe package that contained the channel
+        simulatePackageRemovedBroadcast(PKG, mUid);
+        waitForIdle();
+
+        // Checks that notification history no longer contains the notification.
+        notifs = mBinderService.getHistoricalNotificationsWithAttribution(
+                PKG, mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */);
+        waitForIdle();
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
     public void testNotificationHistory_addNoisyNotification() throws Exception {
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */);
@@ -11472,14 +11532,12 @@
         verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
     }
 
-    private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+    private void verifyStickyHun(int permissionState, boolean appRequested,
             boolean isSticky) throws Exception {
 
         when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
                 PKG, mUserId)).thenReturn(appRequested);
 
-        mTestFlagResolver.setFlagOverride(flag, true);
-
         when(mPermissionManager.checkPermissionForDataDelivery(
                 eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any()))
                 .thenReturn(permissionState);
@@ -11503,8 +11561,7 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
                 /* isSticky= */ true);
     }
 
@@ -11512,16 +11569,14 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
                 /* isSticky= */ true);
     }
 
     @Test
     public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
             throws Exception {
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
                 /* isSticky= */ false);
     }
 
@@ -11530,39 +11585,11 @@
     public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
             throws Exception {
 
-        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
+        verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
                 /* isSticky= */ false);
     }
 
     @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
-    public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun()
-            throws Exception {
-
-        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
-                /* isSticky= */ true);
-    }
-
-    @Test
     public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 5147a08..29848d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -171,19 +171,8 @@
     }
 
     @Test
-    public void testGetFsiState_stickyHunFlagDisabled_zero() {
-        final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ false,
-                /* hasFullScreenIntent= */ true,
-                /* hasFsiRequestedButDeniedFlag= */ true,
-                /* eventType= */ NOTIFICATION_POSTED);
-        assertEquals(0, fsiState);
-    }
-
-    @Test
     public void testGetFsiState_isUpdate_zero() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ true,
                 /* hasFsiRequestedButDeniedFlag= */ true,
                 /* eventType= */ NOTIFICATION_UPDATED);
@@ -193,7 +182,6 @@
     @Test
     public void testGetFsiState_hasFsi_allowedEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ true,
                 /* hasFsiRequestedButDeniedFlag= */ false,
                 /* eventType= */ NOTIFICATION_POSTED);
@@ -203,7 +191,6 @@
     @Test
     public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ false,
                 /* hasFsiRequestedButDeniedFlag= */ true,
                 /* eventType= */ NOTIFICATION_POSTED);
@@ -213,7 +200,6 @@
     @Test
     public void testGetFsiState_noFsi_noFsiEnum() {
         final int fsiState = NotificationRecordLogger.getFsiState(
-                /* isStickyHunFlagEnabled= */ true,
                 /* hasFullScreenIntent= */ false,
                 /* hasFsiRequestedButDeniedFlag= */ false,
                 /* eventType= */ NOTIFICATION_POSTED);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c156e37..fe21103 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -5733,17 +5733,9 @@
     }
 
     @Test
-    public void testGetFsiState_flagDisabled_zero() {
-        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
-
-        assertEquals(0, fsiState);
-    }
-
-    @Test
     public void testGetFsiState_appDidNotRequest_enumNotRequested() {
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission */ false);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
     }
@@ -5754,7 +5746,7 @@
                 .thenReturn(PermissionManager.PERMISSION_GRANTED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission= */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
     }
@@ -5765,7 +5757,7 @@
                 .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission = */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
     }
@@ -5776,7 +5768,7 @@
                 .thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
 
         final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
-                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+                /* requestedFsiPermission = */ true);
 
         assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
     }
@@ -5785,18 +5777,7 @@
     public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
-
-        assertFalse(isUserSet);
-    }
-
-    @Test
-    public void testIsFsiPermissionUserSet_flagDisabled_false() {
-        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
-                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ false);
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertFalse(isUserSet);
     }
@@ -5805,8 +5786,7 @@
     public void testIsFsiPermissionUserSet_userSet_true() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertTrue(isUserSet);
     }
@@ -5815,8 +5795,7 @@
     public void testIsFsiPermissionUserSet_notUserSet_false() {
         final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
                 /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
-                /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
-                /* isStickyHunFlagEnabled= */ true);
+                /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET);
 
         assertFalse(isUserSet);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8f30f41..6976ec3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -82,7 +82,7 @@
     }
 
     @Override
-    protected boolean isCallerIsSystemOrSystemUi() {
+    protected boolean isCallerSystemOrSystemUi() {
         countSystemChecks++;
         return isSystemUid || isSystemAppId;
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 8dcf89b..999e33c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -102,4 +102,18 @@
         assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
         assertThat(copy.shouldDisplayGrayscale()).isFalse();
     }
+
+    @Test
+    public void hasEffects_none_returnsFalse() {
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder().build();
+        assertThat(effects.hasEffects()).isFalse();
+    }
+
+    @Test
+    public void hasEffects_some_returnsTrue() {
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .build();
+        assertThat(effects.hasEffects()).isTrue();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 261b5d3..cad8bac 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -33,6 +33,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenPolicy;
@@ -106,7 +107,7 @@
     }
 
     @Test
-    public void testZenPolicyToNotificationPolicy() {
+    public void testZenPolicyToNotificationPolicy_classic() {
         ZenModeConfig config = getMutedAllConfig();
         config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
 
@@ -139,7 +140,59 @@
     }
 
     @Test
-    public void testZenConfigToZenPolicy() {
+    public void testZenPolicyToNotificationPolicy() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenModeConfig config = getMutedAllConfig();
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
+
+        // Explicitly allow conversations from priority senders to make sure that goes through
+        // Explicitly disallow channels to make sure that goes through, too
+        ZenPolicy zenPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowReminders(true)
+                .allowEvents(true)
+                .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+                .showLights(false)
+                .showInAmbientDisplay(false)
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .build();
+
+        Policy originalPolicy = config.toNotificationPolicy();
+        int priorityCategories = originalPolicy.priorityCategories;
+        int priorityCallSenders = originalPolicy.priorityCallSenders;
+        int priorityMessageSenders = originalPolicy.priorityMessageSenders;
+        int priorityConversationsSenders = CONVERSATION_SENDERS_IMPORTANT;
+        int suppressedVisualEffects = originalPolicy.suppressedVisualEffects;
+        priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+        priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
+        priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
+        priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+        suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+        suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
+
+        Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders,
+                priorityMessageSenders, suppressedVisualEffects,
+                Policy.STATE_PRIORITY_CHANNELS_BLOCKED, priorityConversationsSenders);
+        assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy));
+
+        // make sure allowChannels=false has gotten through correctly (also covered above)
+        assertFalse(expectedPolicy.allowPriorityChannels());
+    }
+
+    @Test
+    public void testZenPolicyToNotificationPolicy_unsetChannelsTakesDefault() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenModeConfig config = new ZenModeConfig();
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().build();
+
+        // When allowChannels is not set to anything in the ZenPolicy builder, make sure it takes
+        // the default value from the zen mode config.
+        Policy policy = config.toNotificationPolicy(zenPolicy);
+        assertEquals(config.allowPriorityChannels, policy.allowPriorityChannels());
+    }
+
+    @Test
+    public void testZenConfigToZenPolicy_classic() {
         ZenPolicy expected = new ZenPolicy.Builder()
                 .allowAlarms(true)
                 .allowReminders(true)
@@ -180,6 +233,51 @@
     }
 
     @Test
+    public void testZenConfigToZenPolicy() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenPolicy expected = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowReminders(true)
+                .allowEvents(true)
+                .showLights(false)
+                .showBadges(false)
+                .showInAmbientDisplay(false)
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+                .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
+                .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE)
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .build();
+
+        ZenModeConfig config = getMutedAllConfig();
+        config.allowAlarms = true;
+        config.allowReminders = true;
+        config.allowEvents = true;
+        config.allowCalls = true;
+        config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS;
+        config.allowMessages = true;
+        config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED;
+        config.allowConversations = false;
+        config.allowPriorityChannels = false;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
+        ZenPolicy actual = config.toZenPolicy();
+
+        assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge());
+        assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms());
+        assertEquals(expected.getPriorityCategoryReminders(),
+                actual.getPriorityCategoryReminders());
+        assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents());
+        assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights());
+        assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient());
+        assertEquals(expected.getPriorityConversationSenders(),
+                actual.getPriorityConversationSenders());
+        assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
+        assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
+        assertEquals(expected.getAllowedChannels(), actual.getAllowedChannels());
+    }
+
+    @Test
     public void testPriorityOnlyMutingAll() {
         ZenModeConfig config = getMutedAllConfig();
         assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
@@ -327,7 +425,6 @@
         rule.conditionId = CONDITION_ID;
         rule.condition = CONDITION;
         rule.enabled = ENABLED;
-        rule.creationTime = 123;
         rule.id = "id";
         rule.zenMode = INTERRUPTION_FILTER;
         rule.modified = true;
@@ -335,6 +432,18 @@
         rule.snoozing = true;
         rule.pkg = OWNER.getPackageName();
         rule.zenPolicy = POLICY;
+        rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(false)
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(false)
+                .setShouldUseNightMode(true)
+                .setShouldDisableAutoBrightness(false)
+                .setShouldDisableTapToWake(true)
+                .setShouldDisableTiltToWake(false)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(false)
+                .setShouldMaximizeDoze(true)
+                .build();
         rule.creationTime = CREATION_TIME;
 
         rule.allowManualInvocation = ALLOW_MANUAL;
@@ -362,6 +471,8 @@
         assertEquals(rule.name, fromXml.name);
         assertEquals(rule.zenMode, fromXml.zenMode);
         assertEquals(rule.creationTime, fromXml.creationTime);
+        assertEquals(rule.zenPolicy, fromXml.zenPolicy);
+        assertEquals(rule.zenDeviceEffects, fromXml.zenDeviceEffects);
 
         assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
         assertEquals(rule.type, fromXml.type);
@@ -439,7 +550,7 @@
     }
 
     @Test
-    public void testZenPolicyXml() throws Exception {
+    public void testZenPolicyXml_classic() throws Exception {
         ZenPolicy policy = new ZenPolicy.Builder()
                 .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
                 .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
@@ -487,6 +598,59 @@
                 fromXml.getVisualEffectNotificationList());
     }
 
+    @Test
+    public void testZenPolicyXml() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy policy = new ZenPolicy.Builder()
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+                .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
+                .allowRepeatCallers(true)
+                .allowAlarms(true)
+                .allowMedia(false)
+                .allowSystem(true)
+                .allowReminders(false)
+                .allowEvents(true)
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .hideAllVisualEffects()
+                .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
+                .build();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writePolicyXml(policy, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenPolicy fromXml = readPolicyXml(bais);
+
+        assertNotNull(fromXml);
+        assertEquals(policy.getPriorityCategoryCalls(), fromXml.getPriorityCategoryCalls());
+        assertEquals(policy.getPriorityCallSenders(), fromXml.getPriorityCallSenders());
+        assertEquals(policy.getPriorityCategoryMessages(), fromXml.getPriorityCategoryMessages());
+        assertEquals(policy.getPriorityMessageSenders(), fromXml.getPriorityMessageSenders());
+        assertEquals(policy.getPriorityCategoryConversations(),
+                fromXml.getPriorityCategoryConversations());
+        assertEquals(policy.getPriorityConversationSenders(),
+                fromXml.getPriorityConversationSenders());
+        assertEquals(policy.getPriorityCategoryRepeatCallers(),
+                fromXml.getPriorityCategoryRepeatCallers());
+        assertEquals(policy.getPriorityCategoryAlarms(), fromXml.getPriorityCategoryAlarms());
+        assertEquals(policy.getPriorityCategoryMedia(), fromXml.getPriorityCategoryMedia());
+        assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
+        assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
+        assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
+        assertEquals(policy.getAllowedChannels(), fromXml.getAllowedChannels());
+
+        assertEquals(policy.getVisualEffectFullScreenIntent(),
+                fromXml.getVisualEffectFullScreenIntent());
+        assertEquals(policy.getVisualEffectLights(), fromXml.getVisualEffectLights());
+        assertEquals(policy.getVisualEffectPeek(), fromXml.getVisualEffectPeek());
+        assertEquals(policy.getVisualEffectStatusBar(), fromXml.getVisualEffectStatusBar());
+        assertEquals(policy.getVisualEffectBadge(), fromXml.getVisualEffectBadge());
+        assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
+        assertEquals(policy.getVisualEffectNotificationList(),
+                fromXml.getVisualEffectNotificationList());
+    }
+
     private ZenModeConfig getMutedRingerConfig() {
         ZenModeConfig config = new ZenModeConfig();
         // Allow alarms, media
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index fd3d5e9b..4e684d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -24,12 +26,16 @@
 import static junit.framework.Assert.fail;
 
 import android.app.AutomaticZenRule;
+import android.app.Flags;
 import android.content.ComponentName;
 import android.net.Uri;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenModeDiff.RuleDiff;
 import android.service.notification.ZenPolicy;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,23 +45,46 @@
 
 import com.android.server.UiServiceTestCase;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class ZenModeDiffTest extends UiServiceTestCase {
+    // Base set of exempt fields independent of fields that are enabled/disabled via flags.
     // version is not included in the diff; manual & automatic rules have special handling
     public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
             Set.of("version", "manualRule", "automaticRules");
 
+    // Differences for flagged fields are only generated if the flag is enabled.
+    // TODO: b/310620812 - Remove this exempt list when flag is inlined.
+    private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
+            android.app.Flags.modesApi()
+                    ? Set.of()
+                    : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
+                            RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
+                            RuleDiff.FIELD_ZEN_DEVICE_EFFECTS);
+
+    // allowPriorityChannels is flagged by android.app.modes_api
+    public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
+            Set.of("allowPriorityChannels");
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testRuleDiff_addRemoveSame() {
         // Test add, remove, and both sides same
@@ -86,7 +115,7 @@
         ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
         ArrayMap<String, Object> expectedTo = new ArrayMap<>();
         List<Field> fieldsForDiff = getFieldsForDiffCheck(
-                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+                ZenModeConfig.ZenRule.class, ZEN_RULE_EXEMPT_FIELDS);
         generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
 
         ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
@@ -131,6 +160,35 @@
         ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
         ArrayMap<String, Object> expectedTo = new ArrayMap<>();
         List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.class, getConfigExemptAndFlaggedFields());
+        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_fieldDiffs_flagOn() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
                 ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
         generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
 
@@ -214,6 +272,14 @@
         assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
     }
 
+    // Helper method that merges the base exempt fields with fields that are flagged
+    private Set getConfigExemptAndFlaggedFields() {
+        Set merged = new HashSet();
+        merged.addAll(ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+        merged.addAll(ZEN_MODE_CONFIG_FLAGGED_FIELDS);
+        return merged;
+    }
+
     // Helper methods for working with configs, policies, rules
     // Just makes a zen rule with fields filled in
     private ZenModeConfig.ZenRule makeRule() {
@@ -230,16 +296,21 @@
         rule.name = "name";
         rule.snoozing = true;
         rule.pkg = "a";
-        rule.allowManualInvocation = true;
-        rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
-        rule.iconResId = 123;
-        rule.triggerDescription = "At night";
+        if (android.app.Flags.modesApi()) {
+            rule.allowManualInvocation = true;
+            rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+            rule.iconResId = 123;
+            rule.triggerDescription = "At night";
+            rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+                    .setShouldDimWallpaper(true)
+                    .build();
+        }
         return rule;
     }
 
     // Get the fields on which we would want to check a diff. The requirements are: not final or/
     // static (as these should/can never change), and not in a specific list that's exempted.
-    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+    private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames)
             throws SecurityException {
         Field[] fields = c.getDeclaredFields();
         ArrayList<Field> out = new ArrayList<>();
@@ -272,7 +343,7 @@
             f.setAccessible(true);
             // Just double-check also that the fields actually are for the class declared
             assertEquals(f.getDeclaringClass(), a.getClass());
-            Class t = f.getType();
+            Class<?> t = f.getType();
             // handle the full set of primitive types first
             if (boolean.class.equals(t)) {
                 f.setBoolean(a, true);
@@ -305,8 +376,8 @@
                 f.set(a, null);
                 expectedA.put(f.getName(), null);
                 try {
-                    f.set(b, t.getDeclaredConstructor().newInstance());
-                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+                    f.set(b, newInstanceOf(t));
+                    expectedB.put(f.getName(), newInstanceOf(t));
                 } catch (Exception e) {
                     // No default constructor, or blithely attempting to construct something doesn't
                     // work for some reason. If the default value isn't null, then keep it.
@@ -321,4 +392,34 @@
             }
         }
     }
+
+    private static Object newInstanceOf(Class<?> clazz) throws ReflectiveOperationException {
+        try {
+            Constructor<?> defaultConstructor = clazz.getDeclaredConstructor();
+            return defaultConstructor.newInstance();
+        } catch (Exception e) {
+            // No default constructor, continue below.
+        }
+
+        // Look for a suitable builder.
+        Optional<Class<?>> clazzBuilder =
+                Arrays.stream(clazz.getDeclaredClasses())
+                        .filter(maybeBuilder -> maybeBuilder.getSimpleName().equals("Builder"))
+                        .filter(maybeBuilder ->
+                                Arrays.stream(maybeBuilder.getMethods()).anyMatch(
+                                        m -> m.getName().equals("build")
+                                                && m.getParameterCount() == 0
+                                                && m.getReturnType().equals(clazz)))
+                        .findFirst();
+        if (clazzBuilder.isPresent()) {
+            Object builder = newInstanceOf(clazzBuilder.get());
+            Method buildMethod = builder.getClass().getMethod("build");
+            Object built = buildMethod.invoke(builder);
+            assertThat(built).isInstanceOf(clazz);
+            return built;
+        }
+
+        throw new ReflectiveOperationException(
+                "Sorry! Couldn't figure out how to create an instance of " + clazz.getName());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index c7905a0..29208f4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -39,13 +39,17 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.Flags;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.media.AudioAttributes;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -58,6 +62,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.Mock;
@@ -76,6 +81,9 @@
 
     private long mTestStartTime;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -526,4 +534,26 @@
                         policy, UserHandle.SYSTEM,
                         different, null, 0, 0, 0));
     }
+
+    @Test
+    public void testAllowChannels_priorityPackage() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        // Notification with package priority = PRIORITY_MAX (assigned to indicate canBypassDnd)
+        NotificationRecord r = getNotificationRecord();
+        r.setPackagePriority(Notification.PRIORITY_MAX);
+
+        // Create a policy to allow channels through, which means shouldIntercept is false
+        ZenModeConfig config = new ZenModeConfig();
+        Policy policy = config.toNotificationPolicy(new ZenPolicy.Builder()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .build());
+        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+
+        // Now create a policy which does not allow priority channels:
+        policy = config.toNotificationPolicy(new ZenPolicy.Builder()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .build());
+        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 37aeb57..97b6b98 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -53,6 +53,9 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.ZenModeHelper.FROM_APP;
+import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI;
+import static com.android.server.notification.ZenModeHelper.FROM_USER;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -108,6 +111,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenModeConfig.ZenRule;
@@ -1645,8 +1649,7 @@
         ZenModeConfig config = new ZenModeConfig();
         config.automaticRules = new ArrayMap<>();
         mZenModeHelper.mConfig = config;
-        mZenModeHelper.updateDefaultZenRules(
-                Process.SYSTEM_UID, true); // shouldn't throw null pointer
+        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); // shouldn't throw null pointer
         mZenModeHelper.pullRules(events); // shouldn't throw null pointer
     }
 
@@ -1671,7 +1674,7 @@
         autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
         mZenModeHelper.mConfig.automaticRules = autoRules;
 
-        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true);
+        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
         assertEquals(updatedDefaultRule,
                 mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
     }
@@ -1697,7 +1700,7 @@
         autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
         mZenModeHelper.mConfig.automaticRules = autoRules;
 
-        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true);
+        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
         assertEquals(updatedDefaultRule,
                 mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
     }
@@ -1724,7 +1727,7 @@
         autoRules.put(SCHEDULE_DEFAULT_RULE_ID, customDefaultRule);
         mZenModeHelper.mConfig.automaticRules = autoRules;
 
-        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true);
+        mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
         ZenModeConfig.ZenRule ruleAfterUpdating =
                 mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID);
         assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled);
@@ -1748,7 +1751,7 @@
             // We need the package name to be something that's not "android" so there aren't any
             // existing rules under that package.
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             assertNotNull(id);
         }
         try {
@@ -1759,7 +1762,7 @@
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1780,7 +1783,7 @@
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             assertNotNull(id);
         }
         try {
@@ -1791,7 +1794,7 @@
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1812,7 +1815,7 @@
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             assertNotNull(id);
         }
         try {
@@ -1823,7 +1826,7 @@
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, false);
+                    CUSTOM_PKG_UID, FROM_APP);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1839,7 +1842,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1860,7 +1863,7 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1884,7 +1887,7 @@
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
         String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, false);
+                CUSTOM_PKG_UID, FROM_APP);
         mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
                 CUSTOM_PKG_UID, false);
@@ -1903,7 +1906,7 @@
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
         String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, false);
+                CUSTOM_PKG_UID, FROM_APP);
 
         AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
                 null,
@@ -1912,7 +1915,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, false);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals("NEW", ruleInConfig.name);
@@ -1928,7 +1931,7 @@
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
         String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, false);
+                CUSTOM_PKG_UID, FROM_APP);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1948,7 +1951,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, false);
+                CUSTOM_PKG_UID, FROM_APP);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1972,13 +1975,13 @@
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         Condition condition = new Condition(sharedUri, "", STATE_TRUE);
         mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
@@ -2010,6 +2013,182 @@
     }
 
     @Test
+    public void addAutomaticZenRule_fromApp_ignoresHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldUseNightMode(true)
+                .setShouldDisableAutoBrightness(true)
+                .setShouldDisableTapToWake(true)
+                .setShouldDisableTiltToWake(true)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(true)
+                .setShouldMaximizeDoze(true)
+                .build();
+
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(zde)
+                        .build(),
+                "reasons", 0, FROM_APP);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(
+                new ZenDeviceEffects.Builder()
+                        .setShouldDisplayGrayscale(true)
+                        .setShouldSuppressAmbientDisplay(true)
+                        .setShouldDimWallpaper(true)
+                        .setShouldUseNightMode(true)
+                        .build());
+    }
+
+    @Test
+    public void addAutomaticZenRule_fromSystem_respectsHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldUseNightMode(true)
+                .setShouldDisableAutoBrightness(true)
+                .setShouldDisableTapToWake(true)
+                .setShouldDisableTiltToWake(true)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(true)
+                .setShouldMaximizeDoze(true)
+                .build();
+
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(zde)
+                        .build(),
+                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
+    }
+
+    @Test
+    public void addAutomaticZenRule_fromUser_respectsHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldUseNightMode(true)
+                .setShouldDisableAutoBrightness(true)
+                .setShouldDisableTapToWake(true)
+                .setShouldDisableTiltToWake(true)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(true)
+                .setShouldMaximizeDoze(true)
+                .build();
+
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(zde)
+                        .build(),
+                "reasons", 0, FROM_USER);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
+    }
+
+    @Test
+    public void updateAutomaticZenRule_fromApp_preservesPreviousHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+                .setShouldDisableTapToWake(true)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(original)
+                        .build(),
+                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+
+        ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
+                .setShouldUseNightMode(true) // Good
+                .setShouldMaximizeDoze(true) // Bad
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId,
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(updateFromApp)
+                        .build(),
+                "reasons", 0, FROM_APP);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(
+                new ZenDeviceEffects.Builder()
+                        .setShouldUseNightMode(true) // From update.
+                        .setShouldDisableTapToWake(true) // From original.
+                        .build());
+    }
+
+    @Test
+    public void updateAutomaticZenRule_fromSystem_updatesHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+                .setShouldDisableTapToWake(true)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(original)
+                        .build(),
+                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+
+        ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
+                .setShouldUseNightMode(true) // Good
+                .setShouldMaximizeDoze(true) // Also good
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId,
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setDeviceEffects(updateFromSystem)
+                        .build(),
+                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
+    }
+
+    @Test
+    public void updateAutomaticZenRule_fromUser_updatesHiddenEffects() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+                .setShouldDisableTapToWake(true)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setDeviceEffects(original)
+                        .build(),
+                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+
+        ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
+                .setShouldUseNightMode(true) // Good
+                .setShouldMaximizeDoze(true) // Also good
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId,
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setDeviceEffects(updateFromUser)
+                        .build(),
+                "reasons", 0, FROM_USER);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
+    }
+
+    @Test
     public void testSetManualZenMode() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
         setupZenConfig();
@@ -2119,7 +2298,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                "test", Process.SYSTEM_UID, true);
+                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(id,
@@ -2128,7 +2307,8 @@
 
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
+                FROM_SYSTEM_OR_SYSTEMUI);
 
         // Add a new system rule
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2138,7 +2318,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
-                "test", Process.SYSTEM_UID, true);
+                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Event 3: turn on the system rule
         mZenModeHelper.setAutomaticZenRuleState(systemId,
@@ -2270,7 +2450,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Rule 2, same as rule 1
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2280,7 +2460,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Rule 3, has stricter settings than the default settings
         ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -2294,7 +2474,7 @@
                 ruleConfig.toZenPolicy(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // First: turn on rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
@@ -2394,7 +2574,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Rule 2, same as rule 1 but owned by the system
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2404,7 +2584,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
@@ -2422,7 +2602,8 @@
         // Disable rule 1. Because this looks like a user action, the UID should not be modified
         // from the system-provided one.
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
+                FROM_SYSTEM_OR_SYSTEMUI);
 
         // Add a manual rule. Any manual rule changes should not get calling uids reassigned.
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
@@ -2553,7 +2734,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // enable the rule
         mZenModeHelper.setAutomaticZenRuleState(id,
@@ -2596,7 +2777,7 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
@@ -2632,7 +2813,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, true);
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // enable rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
@@ -2656,7 +2837,7 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                "test", Process.SYSTEM_UID, true);
+                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
@@ -2678,6 +2859,56 @@
     }
 
     @Test
+    public void testUpdateConsolidatedPolicy_allowChannels() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        setupZenConfig();
+
+        // one rule, custom policy, allows channels
+        ZenPolicy customPolicy = new ZenPolicy.Builder()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .build();
+
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                customPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+
+        // enable the rule; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // confirm that channels make it through
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
+
+        // add new rule with policy that disallows channels
+        ZenPolicy strictPolicy = new ZenPolicy.Builder()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .build();
+
+        AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                strictPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+
+        // enable rule 2; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id2,
+                new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // rule 2 should override rule 1
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
+    }
+
+    @Test
     public void zenRuleToAutomaticZenRule_allFields() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
@@ -2734,7 +2965,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -2750,7 +2981,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
+                FROM_SYSTEM_OR_SYSTEMUI);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -2768,7 +3000,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -2784,7 +3016,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(true);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
+                FROM_SYSTEM_OR_SYSTEMUI);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -2803,7 +3036,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -2839,7 +3072,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -2879,7 +3112,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -2919,7 +3152,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, true);
+                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(createdId,
@@ -2932,7 +3165,8 @@
 
         // Event 3: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
+                FROM_SYSTEM_OR_SYSTEMUI);
 
         assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index b4a294d..2f4f891c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -16,10 +16,14 @@
 
 package com.android.server.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import android.app.Flags;
 import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenPolicy;
 import android.service.notification.nano.DNDPolicyProto;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -30,6 +34,7 @@
 
 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,6 +46,9 @@
 public class ZenPolicyTest extends UiServiceTestCase {
     private static final String CLASS = "android.service.notification.ZenPolicy";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testZenPolicyApplyAllowedToDisallowed() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
@@ -192,6 +200,70 @@
     }
 
     @Test
+    public void testZenPolicyApplyChannels_applyUnset() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy unset = builder.build();
+
+        // priority channels allowed
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy channelsPriority = builder.build();
+
+        // unset applied, channels setting keeps its state
+        channelsPriority.apply(unset);
+        assertThat(channelsPriority.getAllowedChannels())
+                .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+    }
+
+    @Test
+    public void testZenPolicyApplyChannels_applyStricter() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        ZenPolicy none = builder.build();
+
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy priority = builder.build();
+
+        // priority channels (less strict state) cannot override a setting that sets it to none
+        none.apply(priority);
+        assertThat(none.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+    }
+
+    @Test
+    public void testZenPolicyApplyChannels_applyLooser() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        ZenPolicy none = builder.build();
+
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy priority = builder.build();
+
+        // applying a policy with channelType=none overrides priority setting
+        priority.apply(none);
+        assertThat(priority.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+    }
+
+    @Test
+    public void testZenPolicyApplyChannels_applySet() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy unset = builder.build();
+
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy priority = builder.build();
+
+        // applying a policy with a set channel type actually goes through
+        unset.apply(priority);
+        assertThat(unset.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+    }
+
+    @Test
     public void testZenPolicyMessagesInvalid() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
 
@@ -225,6 +297,15 @@
     }
 
     @Test
+    public void testEmptyZenPolicy_emptyChannels() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        ZenPolicy policy = builder.build();
+        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+    }
+
+    @Test
     public void testAllowReminders() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
 
@@ -530,6 +611,35 @@
     }
 
     @Test
+    public void testAllowChannels_noFlag() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_MODES_API);
+
+        // allowChannels should be unset, not be modifiable, and not show up in any output
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy policy = builder.build();
+
+        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+        assertThat(policy.toString().contains("allowChannels")).isFalse();
+    }
+
+    @Test
+    public void testAllowChannels() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        // allow priority channels
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        ZenPolicy policy = builder.build();
+        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+
+        // disallow priority channels
+        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        policy = builder.build();
+        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+    }
+
+    @Test
     public void testTooLongLists_fromParcel() {
         ArrayList<Integer> longList = new ArrayList<Integer>(50);
         for (int i = 0; i < 50; i++) {
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 1b8d746..e83f03d 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -99,4 +99,7 @@
         enabled: false,
     },
 
+    data: [
+        ":OverlayTestApp",
+    ],
 }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 762e23c..c3074bb 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -93,8 +93,6 @@
                   android:showWhenLocked="true"/>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
-        <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" />
-
         <activity android:name="com.android.server.wm.SurfaceSyncGroupTests$TestActivity"
             android:screenOrientation="locked"
             android:turnScreenOn="true"
@@ -119,6 +117,16 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity"
+                  android:exported="true">
+        </activity>
+
+        <activity android:name="com.android.server.wm.utils.TestActivity"
+            android:screenOrientation="locked"
+            android:turnScreenOn="true"
+            android:showWhenLocked="true"
+            android:theme="@style/WhiteBackgroundTheme"
+            android:exported="true" />
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml
index 2717ef90..f8ebead 100644
--- a/services/tests/wmtests/AndroidTest.xml
+++ b/services/tests/wmtests/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
         <option name="test-file-name" value="WmTests.apk" />
+        <option name="test-file-name" value="OverlayTestApp.apk" />
     </target_preparer>
 
     <option name="test-tag" value="WmTests" />
diff --git a/services/tests/wmtests/OverlayApp/Android.bp b/services/tests/wmtests/OverlayApp/Android.bp
new file mode 100644
index 0000000..77d5b22
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/Android.bp
@@ -0,0 +1,19 @@
+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_helper_app {
+    name: "OverlayTestApp",
+
+    srcs: ["**/*.java"],
+
+    resource_dirs: ["res"],
+
+    certificate: "platform",
+    platform_apis: true,
+}
diff --git a/services/tests/wmtests/OverlayApp/AndroidManifest.xml b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
new file mode 100644
index 0000000..5b4ef57
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.server.wm.overlay_app">
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+
+    <application>
+        <activity android:name=".OverlayApp"
+                  android:exported="true"
+                  android:theme="@style/TranslucentFloatingTheme">
+        </activity>
+    </application>
+</manifest>
diff --git a/services/tests/wmtests/OverlayApp/res/values/styles.xml b/services/tests/wmtests/OverlayApp/res/values/styles.xml
new file mode 100644
index 0000000..fff10a3
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/res/values/styles.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <style name="TranslucentFloatingTheme" >
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowNoTitle">true</item>
+
+        <!-- Disables starting window. -->
+        <item name="android:windowDisablePreview">true</item>
+    </style>
+</resources>
diff --git a/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
new file mode 100644
index 0000000..89161c5
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wm.overlay_app;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Test app that is translucent not touchable modal.
+ * If launched with "disableInputSink" extra boolean value, this activity disables
+ * ActivityRecordInputSinkEnabled as long as the permission is granted.
+ */
+public class OverlayApp extends Activity {
+    private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        LinearLayout tv = new LinearLayout(this);
+        tv.setBackgroundColor(Color.GREEN);
+        tv.setPadding(50, 50, 50, 50);
+        tv.setGravity(Gravity.CENTER);
+        setContentView(tv);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        if (getIntent().getBooleanExtra(KEY_DISABLE_INPUT_SINK, false)) {
+            setActivityRecordInputSinkEnabled(false);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 270d5df..ab35da6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,7 +46,6 @@
 import static java.util.Collections.unmodifiableMap;
 
 import android.content.Context;
-import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
@@ -110,8 +109,8 @@
         }
     }
 
-    void sendKeyCombination(int[] keyCodes, long duration, boolean longPress) {
-        final long downTime = SystemClock.uptimeMillis();
+    void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress) {
+        final long downTime = mPhoneWindowManager.getCurrentTime();
         final int count = keyCodes.length;
         int metaState = 0;
 
@@ -126,14 +125,12 @@
             metaState |= MODIFIER.getOrDefault(keyCode, 0);
         }
 
-        try {
-            Thread.sleep(duration);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+        if (durationMillis > 0) {
+            mPhoneWindowManager.moveTimeForward(durationMillis);
         }
 
         if (longPress) {
-            final long nextDownTime = SystemClock.uptimeMillis();
+            final long nextDownTime = mPhoneWindowManager.getCurrentTime();
             for (int i = 0; i < count; i++) {
                 final int keyCode = keyCodes[i];
                 final KeyEvent nextDownEvent = new KeyEvent(downTime, nextDownTime,
@@ -145,7 +142,7 @@
             }
         }
 
-        final long eventTime = SystemClock.uptimeMillis();
+        final long eventTime = mPhoneWindowManager.getCurrentTime();
         for (int i = count - 1; i >= 0; i--) {
             final int keyCode = keyCodes[i];
             final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
@@ -157,8 +154,8 @@
         }
     }
 
-    void sendKeyCombination(int[] keyCodes, long duration) {
-        sendKeyCombination(keyCodes, duration, false /* longPress */);
+    void sendKeyCombination(int[] keyCodes, long durationMillis) {
+        sendKeyCombination(keyCodes, durationMillis, false /* longPress */);
     }
 
     void sendLongPressKeyCombination(int[] keyCodes) {
@@ -170,30 +167,7 @@
     }
 
     void sendKey(int keyCode, boolean longPress) {
-        final long downTime = SystemClock.uptimeMillis();
-        final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
-                0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
-                0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
-        event.setDisplayId(DEFAULT_DISPLAY);
-        interceptKey(event);
-
-        if (longPress) {
-            final long nextDownTime = downTime + ViewConfiguration.getLongPressTimeout();
-            final KeyEvent nextDownevent = new KeyEvent(downTime, nextDownTime,
-                    KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, 0 /*metaState*/,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
-                    KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
-            interceptKey(nextDownevent);
-        }
-
-        final long eventTime = longPress
-                ? SystemClock.uptimeMillis() + ViewConfiguration.getLongPressTimeout()
-                : SystemClock.uptimeMillis();
-        final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
-                0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
-                0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
-        upEvent.setDisplayId(DEFAULT_DISPLAY);
-        interceptKey(upEvent);
+        sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress);
     }
 
     private void interceptKey(KeyEvent keyEvent) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index f2721a5..7ea5010 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -30,9 +30,9 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
-import android.os.Looper;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
 import android.view.KeyEvent;
@@ -109,7 +109,7 @@
                     }
 
                     @Override
-                    public void onPress(long downTime) {
+                    public void onPress(long downTime, int displayId) {
                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                             return;
                         }
@@ -131,7 +131,7 @@
                     }
 
                     @Override
-                    void onMultiPress(long downTime, int count) {
+                    void onMultiPress(long downTime, int count, int displayId) {
                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                             return;
                         }
@@ -141,7 +141,7 @@
                     }
 
                     @Override
-                    void onKeyUp(long eventTime, int multiPressCount) {
+                    void onKeyUp(long eventTime, int multiPressCount, int displayId) {
                         mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
                     }
                 });
@@ -159,7 +159,7 @@
                     }
 
                     @Override
-                    public void onPress(long downTime) {
+                    public void onPress(long downTime, int displayId) {
                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                             return;
                         }
@@ -167,7 +167,7 @@
                     }
 
                     @Override
-                    void onMultiPress(long downTime, int count) {
+                    void onMultiPress(long downTime, int count, int displayId) {
                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
                             return;
                         }
@@ -177,7 +177,7 @@
                     }
 
                     @Override
-                    void onKeyUp(long eventTime, int multiPressCount) {
+                    void onKeyUp(long eventTime, int multiPressCount, int displayId) {
                         mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
                     }
 
@@ -398,7 +398,7 @@
         final SingleKeyGestureDetector.SingleKeyRule rule =
                 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) {
                     @Override
-                    void onPress(long downTime) {
+                    void onPress(long downTime, int displayId) {
                         mShortPressed.countDown();
                     }
                 };
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 314cd04..7788b33 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -99,6 +99,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.OffsettableClock;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.DisplayPolicy;
@@ -162,7 +163,8 @@
     @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
 
     private StaticMockitoSession mMockitoSession;
-    private TestLooper mTestLooper = new TestLooper();
+    private OffsettableClock mClock = new OffsettableClock();
+    private TestLooper mTestLooper = new TestLooper(() -> mClock.now());
     private HandlerThread mHandlerThread;
     private Handler mHandler;
 
@@ -335,6 +337,15 @@
         mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
     }
 
+    long getCurrentTime() {
+        return mClock.now();
+    }
+
+    void moveTimeForward(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
     /**
      * Below functions will override the setting or the policy behavior.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
new file mode 100644
index 0000000..3b280d9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+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.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.window.WindowInfosListenerForTest;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityRecordInputSinkTests {
+    private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app";
+    private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp";
+    private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
+    public final ActivityScenarioRule<TestActivity> mActivityRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
+    private UiAutomation mUiAutomation;
+
+    @Before
+    public void setUp() {
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    }
+
+    @After
+    public void tearDown() {
+        ActivityManager am =
+                InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+                        ActivityManager.class);
+        mUiAutomation.adoptShellPermissionIdentity();
+        try {
+            am.forceStopPackage(OVERLAY_APP_PKG);
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testSimpleButtonPress() {
+        injectTapOnButton();
+
+        mActivityRule.getScenario().onActivity(a -> {
+            assertEquals(1, a.mNumClicked);
+        });
+    }
+
+    @Test
+    public void testSimpleButtonPress_withOverlay() throws InterruptedException {
+        startOverlayApp(false);
+        waitForOverlayApp();
+
+        injectTapOnButton();
+
+        mActivityRule.getScenario().onActivity(a -> {
+            assertEquals(0, a.mNumClicked);
+        });
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+    public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException {
+        startOverlayApp(true);
+        waitForOverlayApp();
+
+        injectTapOnButton();
+
+        mActivityRule.getScenario().onActivity(a -> {
+            assertEquals(1, a.mNumClicked);
+        });
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+    public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled()
+            throws InterruptedException {
+        startOverlayApp(true);
+        waitForOverlayApp();
+
+        injectTapOnButton();
+
+        mActivityRule.getScenario().onActivity(a -> {
+            assertEquals(0, a.mNumClicked);
+        });
+    }
+
+    private void startOverlayApp(boolean disableInputSink) {
+        String launchCommand = "am start -n " + OVERLAY_ACTIVITY;
+        if (disableInputSink) {
+            launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true";
+        }
+
+        mUiAutomation.adoptShellPermissionIdentity();
+        try {
+            mUiAutomation.executeShellCommand(launchCommand);
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private void waitForOverlayApp() throws InterruptedException {
+        final var listenerHost = new WindowInfosListenerForTest();
+        final var latch = new CountDownLatch(1);
+        final Consumer<List<WindowInfosListenerForTest.WindowInfo>> listener = windowInfos -> {
+            final boolean inputSinkReady = windowInfos.stream().anyMatch(info ->
+                    info.isVisible
+                            && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY));
+            if (inputSinkReady) {
+                latch.countDown();
+            }
+        };
+
+        listenerHost.addWindowInfosListener(listener);
+        try {
+            assertTrue(latch.await(5, TimeUnit.SECONDS));
+        } finally {
+            listenerHost.removeWindowInfosListener(listener);
+        }
+    }
+
+    private void injectTapOnButton() {
+        Rect buttonBounds = new Rect();
+        mActivityRule.getScenario().onActivity(a -> {
+            a.mButton.getBoundsOnScreen(buttonBounds);
+        });
+        final int x = buttonBounds.centerX();
+        final int y = buttonBounds.centerY();
+
+        MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
+        mUiAutomation.injectInputEvent(down, true);
+
+        SystemClock.sleep(10);
+
+        MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_UP, x, y, 0);
+        mUiAutomation.injectInputEvent(up, true);
+
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    public static class TestActivity extends Activity {
+        int mNumClicked = 0;
+        Button mButton;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mButton = new Button(this);
+            mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT));
+            setContentView(mButton);
+            mButton.setOnClickListener(v -> mNumClicked++);
+        }
+    }
+}
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 1776ba5..f2e54bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -490,7 +490,7 @@
         ensureActivityConfiguration(activity);
 
         verify(mClientLifecycleManager, never())
-                .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class));
+                .scheduleTransactionItem(any(), isA(ActivityConfigurationChangeItem.class));
     }
 
     @Test
@@ -519,7 +519,7 @@
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
-        verify(mClientLifecycleManager).scheduleTransaction(
+        verify(mClientLifecycleManager).scheduleTransactionItem(
                 eq(activity.app.getThread()), eq(expected));
     }
 
@@ -592,7 +592,7 @@
         assertEquals(expectedOrientation, currentConfig.orientation);
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(activity.token, currentConfig);
-        verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
+        verify(mClientLifecycleManager).scheduleTransactionItem(activity.app.getThread(), expected);
         verify(displayRotation).onSetRequestedOrientation();
     }
 
@@ -812,7 +812,8 @@
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(activity.token,
                             activity.getConfiguration());
-            verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
+            verify(mClientLifecycleManager).scheduleTransactionItem(
+                    activity.app.getThread(), expected);
         } finally {
             stack.getDisplayArea().removeChild(stack);
         }
@@ -1174,10 +1175,12 @@
     @Test
     public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
         registerTestTransitionPlayer();
+        spyOn(mRootWindowContainer.mTransitionController);
+        final ActivityRecord bottomActivity = createActivityWithTask();
+        bottomActivity.setVisibility(false);
+        bottomActivity.setState(STOPPED, "test");
+        bottomActivity.mLastSurfaceShowing = false;
         final ActivityRecord activity = createActivityWithTask();
-        // Put an activity on top of test activity to make it invisible and prevent us from
-        // accidentally resuming the topmost one again.
-        new ActivityBuilder(mAtm).build();
         activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
@@ -1185,6 +1188,14 @@
 
         verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
         assertFalse(activity.inTransition());
+
+        // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle
+        // -> resumeFocusedTasksTopActivities
+        assertTrue(bottomActivity.isState(RESUMED));
+        assertTrue(bottomActivity.isVisible());
+        verify(mRootWindowContainer.mTransitionController).onVisibleWithoutCollectingTransition(
+                eq(bottomActivity), any());
+        assertTrue(bottomActivity.mLastSurfaceShowing);
     }
 
     /**
@@ -1785,7 +1796,7 @@
             clearInvocations(mClientLifecycleManager);
             activity.getTask().removeImmediately("test");
             try {
-                verify(mClientLifecycleManager).scheduleTransaction(any(),
+                verify(mClientLifecycleManager).scheduleTransactionItem(any(),
                         isA(DestroyActivityItem.class));
             } catch (RemoteException ignored) {
             }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e2bb115..98055fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -118,6 +118,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.wm.utils.MockTracker;
 
@@ -1378,7 +1379,8 @@
                 .setUserId(10)
                 .build();
 
-        final int result = starter.recycleTask(task, null, null, null);
+        final int result = starter.recycleTask(task, null, null, null,
+                BalVerdict.ALLOW_BY_DEFAULT);
         assertThat(result == START_SUCCESS).isTrue();
         assertThat(starter.mAddingToTask).isTrue();
     }
@@ -1892,7 +1894,7 @@
         starter.startActivityInner(target, source, null /* voiceSession */,
                 null /* voiceInteractor */, 0 /* startFlags */,
                 options, inTask, inTaskFragment,
-                BackgroundActivityStartController.BAL_ALLOW_DEFAULT, null /* intentGrants */,
-                -1 /* realCallingUid */);
+                BalVerdict.ALLOW_BY_DEFAULT,
+                null /* intentGrants */, -1 /* realCallingUid */);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 3c027ff..d2c731c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -127,7 +127,7 @@
 
         final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor =
                 ArgumentCaptor.forClass(ClientTransactionItem.class);
-        verify(mockLifecycleManager).scheduleTransaction(any(),
+        verify(mockLifecycleManager).scheduleTransactionItem(any(),
                 clientTransactionItemCaptor.capture());
         final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue();
         // Check that only an enter pip request item callback was scheduled.
@@ -144,7 +144,7 @@
 
         mAtm.mActivityClientController.requestPictureInPictureMode(activity);
 
-        verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any());
     }
 
     @Test
@@ -156,7 +156,7 @@
 
         mAtm.mActivityClientController.requestPictureInPictureMode(activity);
 
-        verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index b44d52e..2034751 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -201,6 +201,33 @@
         verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask();
     }
 
+    /** Verifies that the activity can be destroying after removing task. */
+    @Test
+    public void testRemoveTask() {
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity1.setVisible(false);
+        activity1.finishing = true;
+        activity1.setState(ActivityRecord.State.STOPPING, "test");
+        activity1.addToStopping(false /* scheduleIdle */, false /* idleDelayed */, "test");
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity2.setState(ActivityRecord.State.RESUMED, "test");
+        // The state can happen from ActivityRecord#makeInvisible.
+        activity2.addToStopping(false /* scheduleIdle */, false /* idleDelayed */, "test");
+        mSupervisor.removeTask(activity1.getTask(), true /* killProcess */,
+                true /* removeFromRecents */, "testRemoveTask");
+        mSupervisor.removeTask(activity2.getTask(), true /* killProcess */,
+                true /* removeFromRecents */, "testRemoveTask");
+
+        assertEquals(ActivityRecord.State.DESTROYING, activity2.getState());
+        assertEquals(ActivityRecord.State.STOPPING, activity1.getState());
+        assertTrue(mSupervisor.mStoppingActivities.contains(activity1));
+        // Assume that it is called by scheduleIdle from addToStopping. And because
+        // mStoppingActivities remembers the finishing activity, it can continue to destroy.
+        mSupervisor.processStoppingAndFinishingActivities(null /* launchedActivity */,
+                false /* processPausingActivities */, "test");
+        assertEquals(ActivityRecord.State.DESTROYING, activity1.getState());
+    }
+
     /** Ensures that the calling package name passed to client complies with package visibility. */
     @Test
     public void testFilteredReferred() {
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 a18dbaf..04aa981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,18 +16,32 @@
 
 package com.android.server.wm;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+
 import android.app.IApplicationThread;
+import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Build/Install/Run:
@@ -37,23 +51,77 @@
 @Presubmit
 public class ClientLifecycleManagerTests {
 
-    @Test
-    public void testScheduleAndRecycleBinderClientTransaction() throws Exception {
-        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class)));
+    @Mock
+    private IApplicationThread mClient;
+    @Mock
+    private IApplicationThread.Stub mNonBinderClient;
+    @Mock
+    private ClientTransactionItem mTransactionItem;
+    @Mock
+    private ActivityLifecycleItem mLifecycleItem;
+    @Captor
+    private ArgumentCaptor<ClientTransaction> mTransactionCaptor;
 
-        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
-        clientLifecycleManager.scheduleTransaction(item);
+    private ClientLifecycleManager mLifecycleManager;
 
-        verify(item, times(1)).recycle();
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mLifecycleManager = spy(new ClientLifecycleManager());
+
+        doReturn(true).when(mLifecycleItem).isActivityLifecycleItem();
     }
 
     @Test
-    public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception {
-        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class)));
+    public void testScheduleTransaction_recycleBinderClientTransaction() throws Exception {
+        final ClientTransaction item = spy(ClientTransaction.obtain(mClient));
 
-        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
-        clientLifecycleManager.scheduleTransaction(item);
+        mLifecycleManager.scheduleTransaction(item);
 
-        verify(item, times(0)).recycle();
+        verify(item).recycle();
+    }
+
+    @Test
+    public void testScheduleTransaction_notRecycleNonBinderClientTransaction() throws Exception {
+        final ClientTransaction item = spy(ClientTransaction.obtain(mNonBinderClient));
+
+        mLifecycleManager.scheduleTransaction(item);
+
+        verify(item, never()).recycle();
+    }
+
+    @Test
+    public void testScheduleTransactionItem() throws RemoteException {
+        doNothing().when(mLifecycleManager).scheduleTransaction(any());
+        mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem);
+
+        verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
+        ClientTransaction transaction = mTransactionCaptor.getValue();
+        assertEquals(1, transaction.getCallbacks().size());
+        assertEquals(mTransactionItem, transaction.getCallbacks().get(0));
+        assertNull(transaction.getLifecycleStateRequest());
+        assertNull(transaction.getTransactionItems());
+
+        clearInvocations(mLifecycleManager);
+        mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem);
+
+        verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
+        transaction = mTransactionCaptor.getValue();
+        assertNull(transaction.getCallbacks());
+        assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest());
+    }
+
+    @Test
+    public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
+        doNothing().when(mLifecycleManager).scheduleTransaction(any());
+        mLifecycleManager.scheduleTransactionAndLifecycleItems(mClient, mTransactionItem,
+                mLifecycleItem);
+
+        verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
+        final ClientTransaction transaction = mTransactionCaptor.getValue();
+        assertEquals(1, transaction.getCallbacks().size());
+        assertEquals(mTransactionItem, transaction.getCallbacks().get(0));
+        assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 9f58491..9efbe35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -25,8 +25,6 @@
 import static com.android.server.wm.utils.LastCallVerifier.lastCall;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,11 +34,14 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
 import com.android.server.testutils.StubTransaction;
 import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -122,7 +123,8 @@
         }
     }
 
-    static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+    static class MockAnimationAdapterFactory extends DimmerAnimationHelper.AnimationAdapterFactory {
+        @Override
         public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
                 SurfaceAnimationRunner runner) {
             return sTestAnimation;
@@ -175,8 +177,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         final float alpha = 0.7f;
         final int blur = 50;
         mHost.addChild(mChild, 0);
@@ -195,8 +197,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         final float alpha = 0.7f;
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, alpha, 20);
@@ -210,8 +212,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
@@ -230,8 +232,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
@@ -290,8 +292,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testRemoveDimImmediately_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 2);
         mDimmer.adjustRelativeLayer(mChild, -1);
@@ -310,8 +312,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testRemoveDimImmediately_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 0);
         mDimmer.adjustRelativeLayer(mChild, -1);
@@ -330,8 +332,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimmerWithBlurUpdatesTransaction_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final int blurRadius = 50;
@@ -344,4 +346,120 @@
         verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
         verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
+
+    /**
+     * mChild is requesting the dim values to be set directly. In this case, dim won't play the
+     * standard animation, but directly apply mChild's requests to the dim surface
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testContainerDimsOpeningAnimationByItself() {
+        mHost.addChild(mChild, 0);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.1f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.2f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.3f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(mTransaction).setAlpha(dimLayer, 0.2f);
+        verify(mTransaction).setAlpha(dimLayer, 0.3f);
+        verify(sTestAnimation, times(1)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+    }
+
+    /**
+     * Same as testContainerDimsOpeningAnimationByItself, but this is a more specific case in which
+     * alpha is animated to 0. This corner case is needed to verify that the layer is removed anyway
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testContainerDimsClosingAnimationByItself() {
+        mHost.addChild(mChild, 0);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.2f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.1f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction).remove(dimLayer);
+    }
+
+    /**
+     * Check the handover of the dim between two windows and the consequent dim animation in between
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testMultipleContainersDimmingConsecutively() {
+        TestWindowContainer first = mChild;
+        TestWindowContainer second = new TestWindowContainer(mWm);
+        mHost.addChild(first, 0);
+        mHost.addChild(second, 1);
+
+        mDimmer.adjustAppearance(first, 0.5f, 0);
+        mDimmer.adjustRelativeLayer(first, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(second, 0.9f, 0);
+        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(sTestAnimation, times(2)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).setAlpha(dimLayer, 0.5f);
+        verify(mTransaction).setAlpha(dimLayer, 0.9f);
+    }
+
+    /**
+     * Two windows are trying to modify the dim at the same time, but only the last request before
+     * updateDims will be satisfied
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testMultipleContainersDimmingAtTheSameTime() {
+        TestWindowContainer first = mChild;
+        TestWindowContainer second = new TestWindowContainer(mWm);
+        mHost.addChild(first, 0);
+        mHost.addChild(second, 1);
+
+        mDimmer.adjustAppearance(first, 0.5f, 0);
+        mDimmer.adjustRelativeLayer(first, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.adjustAppearance(second, 0.9f, 0);
+        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(sTestAnimation, times(1)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction, never()).setAlpha(dimLayer, 0.5f);
+        verify(mTransaction).setAlpha(dimLayer, 0.9f);
+    }
 }
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 5b88c8c..acce2e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -51,8 +51,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -159,6 +157,10 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tests for the {@link DisplayContent} class.
@@ -643,6 +645,21 @@
     }
 
     @Test
+    public void testDisplayHasContent() {
+        final WindowState window = createWindow(null, TYPE_APPLICATION_OVERLAY, "window");
+        setDrawnState(WindowStateAnimator.COMMIT_DRAW_PENDING, window);
+        assertFalse(mDisplayContent.getLastHasContent());
+        // The pending draw state should be committed and the has-content state is also updated.
+        mDisplayContent.applySurfaceChangesTransaction();
+        assertTrue(window.isDrawn());
+        assertTrue(mDisplayContent.getLastHasContent());
+        // If the only window is no longer visible, has-content will be false.
+        setDrawnState(WindowStateAnimator.NO_SURFACE, window);
+        mDisplayContent.applySurfaceChangesTransaction();
+        assertFalse(mDisplayContent.getLastHasContent());
+    }
+
+    @Test
     public void testImeIsAttachedToDisplayForLetterboxedApp() {
         final DisplayContent dc = mDisplayContent;
         final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window");
@@ -2272,7 +2289,7 @@
                 0 /* userId */);
         dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test";
         // Trigger display changed.
-        dc.onDisplayChanged();
+        updateDisplay(dc);
         // Ensure overridden size and denisty match the most up-to-date values in settings for the
         // display.
         verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
@@ -2747,26 +2764,6 @@
                 mDisplayContent.getKeepClearAreas());
     }
 
-    @Test
-    public void testMayImeShowOnLaunchingActivity_negativeWhenSoftInputModeHidden() {
-        final ActivityRecord app = createActivityRecord(mDisplayContent);
-        final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, app, "appWin");
-        createWindow(null, TYPE_APPLICATION_STARTING, app, "startingWin");
-        app.mStartingData = mock(SnapshotStartingData.class);
-        // Assume the app has shown IME before and warm launching with a snapshot window.
-        doReturn(true).when(app.mStartingData).hasImeSurface();
-
-        // Expect true when this IME focusable activity will show IME during launching.
-        assertTrue(WindowManager.LayoutParams.mayUseInputMethod(appWin.mAttrs.flags));
-        assertTrue(mDisplayContent.mayImeShowOnLaunchingActivity(app));
-
-        // Not expect IME will be shown during launching if the app's softInputMode is hidden.
-        appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
-        assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app));
-        appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_HIDDEN;
-        assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app));
-    }
-
     private void removeRootTaskTests(Runnable runnable) {
         final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -2837,7 +2834,7 @@
      */
     private DisplayContent createDisplayNoUpdateDisplayInfo() {
         final DisplayContent displayContent = createNewDisplay();
-        doNothing().when(displayContent).updateDisplayInfo();
+        doNothing().when(displayContent).updateDisplayInfo(any());
         return displayContent;
     }
 
@@ -2867,6 +2864,16 @@
         return result;
     }
 
+    private void updateDisplay(DisplayContent displayContent) {
+        CompletableFuture<Object> future = new CompletableFuture<>();
+        displayContent.requestDisplayUpdate(() -> future.complete(new Object()));
+        try {
+            future.get(15, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private void tapOnDisplay(final DisplayContent dc) {
         final DisplayMetrics dm = dc.getDisplayMetrics();
         final float x = dm.widthPixels / 2;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c782d3e..be96e60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -274,6 +274,26 @@
         assertEquals(mAppWindow, policy.getTopFullscreenOpaqueWindow());
     }
 
+    @SetupWindows(addWindows = W_NOTIFICATION_SHADE)
+    @Test
+    public void testVisibleProcessWhileDozing() {
+        final WindowProcessController wpc = mNotificationShadeWindow.getProcess();
+        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        policy.addWindowLw(mNotificationShadeWindow, mNotificationShadeWindow.mAttrs);
+
+        policy.screenTurnedOff();
+        policy.setAwake(false);
+        policy.screenTurnedOn(null /* screenOnListener */);
+        assertTrue(wpc.isShowingUiWhileDozing());
+        policy.screenTurnedOff();
+        assertFalse(wpc.isShowingUiWhileDozing());
+
+        policy.screenTurnedOn(null /* screenOnListener */);
+        assertTrue(wpc.isShowingUiWhileDozing());
+        policy.setAwake(true);
+        assertFalse(wpc.isShowingUiWhileDozing());
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testMainAppWindowDisallowFitSystemWindowTypes() {
         final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 2af6745..7601868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -30,6 +30,7 @@
 import static android.view.Surface.ROTATION_90;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -43,12 +44,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
-import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.RefreshCallbackItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.content.ComponentName;
@@ -133,13 +131,17 @@
                 });
         mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
                 mDisplayContent, mMockHandler);
+
+        // Do not show the real toast.
+        spyOn(mDisplayRotationCompatPolicy);
+        doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt());
+        doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt(), anyString());
     }
 
     @Test
     public void testOpenedCameraInSplitScreen_showToast() throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         spyOn(mTask);
-        spyOn(mDisplayRotationCompatPolicy);
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
 
@@ -163,7 +165,6 @@
     public void testOpenedCameraInSplitScreen_orientationNotFixed_doNotShowToast() {
         configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
         spyOn(mTask);
-        spyOn(mDisplayRotationCompatPolicy);
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
         doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
 
@@ -178,7 +179,6 @@
     public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
         when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
-        spyOn(mDisplayRotationCompatPolicy);
 
         mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
 
@@ -188,8 +188,6 @@
 
     @Test
     public void testOnScreenRotationAnimationFinished_noOpenCamera_doNotShowToast() {
-        spyOn(mDisplayRotationCompatPolicy);
-
         mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
 
         verify(mDisplayRotationCompatPolicy, never()).showToast(
@@ -200,7 +198,6 @@
     public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         doReturn(true).when(mActivity).inMultiWindowMode();
-        spyOn(mDisplayRotationCompatPolicy);
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
@@ -214,7 +211,6 @@
     public void testOnScreenRotationAnimationFinished_orientationNotFixed_doNotShowToast() {
         configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        spyOn(mDisplayRotationCompatPolicy);
 
         mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
 
@@ -226,7 +222,6 @@
     public void testOnScreenRotationAnimationFinished_showToast() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        spyOn(mDisplayRotationCompatPolicy);
 
         mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
 
@@ -529,8 +524,8 @@
     public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat())
-                .thenReturn(true);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
@@ -571,14 +566,14 @@
         verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
                 .setIsRefreshAfterRotationRequested(true);
 
-        final ClientTransaction transaction = ClientTransaction.obtain(mActivity.app.getThread());
-        transaction.addCallback(RefreshCallbackItem.obtain(mActivity.token,
-                cycleThroughStop ? ON_STOP : ON_PAUSE));
-        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(mActivity.token,
-                /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+                cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
 
         verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
-                .scheduleTransaction(eq(transaction));
+                .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+                        refreshCallbackItem, resumeActivityItem);
     }
 
     private void assertNoForceRotationOrRefresh() throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index cf620fe..c404c77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -186,7 +187,7 @@
                 /* options */null,
                 /* inTask */null,
                 /* inTaskFragment */ null,
-                /* balCode */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT,
+                BalVerdict.ALLOW_BY_DEFAULT,
                 /* intentGrants */null,
                 /* realCaiingUid */ -1);
 
@@ -216,7 +217,7 @@
                 /* options= */null,
                 /* inTask= */null,
                 /* inTaskFragment= */ null,
-                /* balCode= */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT,
+                BalVerdict.ALLOW_BY_DEFAULT,
                 /* intentGrants= */null,
                 /* realCaiingUid */ -1);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 2b19ad9..1be61c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -114,8 +115,7 @@
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
                 .setUniqueId(mDisplayUniqueId).build();
-        when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
-                .thenReturn(mTestDisplay);
+        doReturn(mTestDisplay).when(mRootWindowContainer).getDisplayContent(mDisplayUniqueId);
 
         Task rootTask = mTestDisplay.getDefaultTaskDisplayArea()
                 .createRootTask(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true);
@@ -200,7 +200,7 @@
     public void testReturnsEmptyDisplayIfDisplayIsNotFound() {
         mTarget.saveTask(mTestTask);
 
-        when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))).thenReturn(null);
+        doReturn(null).when(mRootWindowContainer).getDisplayContent(eq(mDisplayUniqueId));
 
         mTarget.getLaunchParams(mTestTask, null, mResult);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index eb78906..5a43498 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -317,6 +317,31 @@
         assertTrue(firstActivity.mRequestForceTransition);
     }
 
+    @Test
+    public void testMultipleActivitiesTaskEnterPip() {
+        // Enable shell transition because the order of setting windowing mode is different.
+        registerTestTransitionPlayer();
+        final ActivityRecord transientActivity = new ActivityBuilder(mAtm)
+                .setCreateTask(true).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm)
+                .setTask(activity1.getTask()).build();
+        activity2.setState(RESUMED, "test");
+
+        // Assume the top activity switches to a transient-launch, e.g. recents.
+        transientActivity.setState(RESUMED, "test");
+        transientActivity.getTask().moveToFront("test");
+
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity2,
+                null /* launchIntoPipHostActivity */, "test");
+        assertEquals("Created PiP task must not change focus", transientActivity.getTask(),
+                mRootWindowContainer.getTopDisplayFocusedRootTask());
+        final Task newPipTask = activity2.getTask();
+        assertEquals(newPipTask, mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask());
+        assertNotEquals(newPipTask, activity1.getTask());
+        assertFalse("Created PiP task must not be in recents", newPipTask.inRecents);
+    }
+
     /**
      * When there is only one activity in the Task, and the activity is requesting to enter PIP, the
      * whole Task should enter PIP.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index 8119fd4..3b9ed26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -23,15 +23,14 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
 import android.view.Gravity;
 import android.view.IWindow;
 import android.view.SurfaceControl;
@@ -46,12 +45,12 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.server.wm.utils.CommonUtils;
+import com.android.server.wm.utils.TestActivity;
 
 import org.junit.After;
 import org.junit.Before;
@@ -59,11 +58,14 @@
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @Presubmit
 @SmallTest
 @RunWith(WindowTestRunner.class)
 public class SurfaceControlViewHostTests {
+    private static final long WAIT_TIME_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
+
     private static final String TAG = "SurfaceControlViewHostTests";
 
     private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
@@ -76,6 +78,8 @@
     private SurfaceControlViewHost mScvh1;
     private SurfaceControlViewHost mScvh2;
 
+    private SurfaceView mSurfaceView;
+
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -96,15 +100,17 @@
         mView1 = new Button(mActivity);
         mView2 = new Button(mActivity);
 
-        mInstrumentation.runOnMainSync(() -> {
-            try {
-                mActivity.attachToSurfaceView(sc);
-            } catch (InterruptedException e) {
-            }
+        CountDownLatch svReadyLatch = new CountDownLatch(1);
+        mActivity.runOnUiThread(() -> addSurfaceView(svReadyLatch));
+        assertTrue("Failed to wait for SV to get created",
+                svReadyLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
+        new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
+                .show(sc).apply();
 
+        mInstrumentation.runOnMainSync(() -> {
             TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
                     mActivity.getResources().getConfiguration(), sc,
-                    mActivity.mSurfaceView.getHostToken());
+                    mSurfaceView.getHostToken());
 
             mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
                     wwm, "requestFocusWithMultipleWindows");
@@ -135,7 +141,7 @@
         }
         assertTrue("Failed to wait for view2", wasVisible);
 
-        IWindow window = IWindow.Stub.asInterface(mActivity.mSurfaceView.getWindowToken());
+        IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken());
 
         WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
                 mScvh1.getInputTransferToken(), true);
@@ -162,43 +168,30 @@
         }
     }
 
-    public static class TestActivity extends Activity implements SurfaceHolder.Callback {
-        private SurfaceView mSurfaceView;
-        private final CountDownLatch mSvReadyLatch = new CountDownLatch(1);
+    private void addSurfaceView(CountDownLatch svReadyLatch) {
+        final FrameLayout content = mActivity.getParentLayout();
+        mSurfaceView = new SurfaceView(mActivity);
+        mSurfaceView.setZOrderOnTop(true);
+        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
+                Gravity.LEFT | Gravity.TOP);
+        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(@NonNull SurfaceHolder holder) {
+                svReadyLatch.countDown();
+            }
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final FrameLayout content = new FrameLayout(this);
-            mSurfaceView = new SurfaceView(this);
-            mSurfaceView.setBackgroundColor(Color.BLACK);
-            mSurfaceView.setZOrderOnTop(true);
-            final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
-                    Gravity.LEFT | Gravity.TOP);
-            content.addView(mSurfaceView, lp);
-            setContentView(content);
-            mSurfaceView.getHolder().addCallback(this);
-        }
+            @Override
+            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+                    int height) {
 
-        @Override
-        public void surfaceCreated(@NonNull SurfaceHolder holder) {
-            mSvReadyLatch.countDown();
-        }
+            }
 
-        @Override
-        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
-                int height) {
-        }
+            @Override
+            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
 
-        @Override
-        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
-        }
-
-        public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException {
-            mSvReadyLatch.await();
-            new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
-                    .show(sc).apply();
-        }
+            }
+        });
+        content.addView(mSurfaceView, lp);
     }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 3cb4a1d..e65a9fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -19,6 +19,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import android.os.Handler;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 
@@ -39,6 +41,9 @@
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(
             this::onBeforeSystemServicesCreated);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @WindowTestRunner.MethodWrapperRule
     public final WindowManagerGlobalLockRule mLockRule =
             new WindowManagerGlobalLockRule(mSystemServicesTestRule);
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 9af5ba5..51f0404 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -38,6 +38,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
@@ -57,7 +58,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.hardware.devicestate.DeviceStateManager;
-import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.Uri;
 import android.os.Handler;
@@ -69,6 +70,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.Log;
+import android.view.DisplayInfo;
 import android.view.InputChannel;
 import android.view.SurfaceControl;
 
@@ -101,6 +103,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -239,6 +242,12 @@
         doNothing().when(contentResolver)
                 .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
                         anyInt());
+
+        // Unit test should not register listener to the real service.
+        final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
+        spyOn(dmg);
+        doNothing().when(dmg).registerDisplayListener(
+                any(), any(Executor.class), anyLong(), anyString());
     }
 
     private void setUpLocalServices() {
@@ -267,6 +276,12 @@
         // DisplayManagerInternal
         final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class);
         doReturn(dmi).when(() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
+        doAnswer(invocation -> {
+            int displayId = invocation.getArgument(0);
+            DisplayInfo displayInfo = invocation.getArgument(1);
+            mWmService.mRoot.getDisplayContent(displayId).getDisplay().getDisplayInfo(displayInfo);
+            return null;
+        }).when(dmi).getNonOverrideDisplayInfo(anyInt(), any());
 
         // ColorDisplayServiceInternal
         final ColorDisplayService.ColorDisplayServiceInternal cds =
@@ -376,9 +391,6 @@
 
         mWmService.onInitReady();
         mAtmService.setWindowManager(mWmService);
-        // Avoid real display events interfering with the test. Also avoid leaking registration.
-        mContext.getSystemService(DisplayManager.class)
-                .unregisterDisplayListener(mAtmService.mRootWindowContainer);
         mWmService.mDisplayEnabled = true;
         mWmService.mDisplayReady = true;
         mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
@@ -432,7 +444,9 @@
         SurfaceAnimationThread.dispose();
         AnimationThread.dispose();
         UiThread.dispose();
-        mInputChannel.dispose();
+        if (mInputChannel != null) {
+            mInputChannel.dispose();
+        }
 
         tearDownLocalServices();
         // Reset priority booster because animation thread has been changed.
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 013be25..5e531b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -574,6 +574,7 @@
         spyOn(root);
         spyOn(root.mLetterboxUiController);
 
+        doReturn(true).when(root).fillsParent();
         doReturn(true).when(root.mLetterboxUiController)
                 .shouldEnableUserAspectRatioSettings();
         doReturn(false).when(root).inSizeCompatMode();
@@ -596,6 +597,12 @@
         assertFalse(task.getTaskInfo()
                 .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
         doReturn(false).when(root).inSizeCompatMode();
+
+        // When the top activity is transparent, the button is not enabled
+        doReturn(false).when(root).fillsParent();
+        assertFalse(task.getTaskInfo()
+                .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
+        doReturn(true).when(root).fillsParent();
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index 2d5b72b..d183cf7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -60,6 +60,11 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 /**
  * Build/Install/Run:
  *  atest WmTests:WindowContextListenerControllerTests
@@ -309,7 +314,7 @@
             return null;
         }).when(display).getDisplayInfo(any(DisplayInfo.class));
 
-        mContainer.getDisplayContent().onDisplayChanged();
+        updateDisplay(mContainer.getDisplayContent());
 
         assertThat(mockToken.mConfiguration).isEqualTo(config1);
         assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId());
@@ -352,4 +357,14 @@
             mRemoved = true;
         }
     }
+
+    private void updateDisplay(DisplayContent displayContent) {
+        CompletableFuture<Object> future = new CompletableFuture<>();
+        displayContent.requestDisplayUpdate(() -> future.complete(new Object()));
+        try {
+            future.get(15, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index d08ab51..f99b489 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -273,7 +273,6 @@
         assertFalse(win.mHasSurface);
         assertNull(win.mWinAnimator.mSurfaceController);
 
-        doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
         // Invisible requested activity should not get the last config even if its view is visible.
         mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
                 outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
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 46cff8b..e31ee11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,7 +306,7 @@
 
     @Test
     public void testCachedStateConfigurationChange() throws RemoteException {
-        doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any());
+        doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
         final IApplicationThread thread = mWpc.getThread();
         final Configuration newConfig = new Configuration(mWpc.getConfiguration());
         newConfig.densityDpi += 100;
@@ -314,26 +314,25 @@
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         clearInvocations(mClientLifecycleManager);
         mWpc.onConfigurationChanged(newConfig);
-        verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any());
+        verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), any());
 
         // Cached state won't send the change.
         clearInvocations(mClientLifecycleManager);
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
         newConfig.densityDpi += 100;
         mWpc.onConfigurationChanged(newConfig);
-        verify(mClientLifecycleManager, never()).scheduleTransaction(eq(thread), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(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).scheduleTransaction(eq(thread), captor.capture());
+        verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+                eq(thread), captor.capture());
         final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
         captor.getValue().preExecute(client);
-        final ArgumentCaptor<Configuration> configCaptor =
-                ArgumentCaptor.forClass(Configuration.class);
-        verify(client).updatePendingConfiguration(configCaptor.capture());
-        assertEquals(newConfig, configCaptor.getValue());
+        verify(client).updatePendingConfiguration(newConfig);
     }
 
     @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 67384b2..2007f68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -757,7 +757,6 @@
         startingApp.getWindowFrames().setInsetsChanged(true);
         startingApp.updateResizingWindowIfNeeded();
         assertTrue(startingApp.isDrawn());
-        assertFalse(startingApp.getOrientationChanging());
     }
 
     @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@@ -1369,7 +1368,7 @@
         assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
 
         // Scenario 3: test removeWindow to remove the Ime layering target overlay window.
-        mWm.removeWindow(session, client);
+        mWm.removeClientToken(session, client.asBinder());
         waitHandlerIdle(mWm.mH);
 
         assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
new file mode 100644
index 0000000..c12dcdd
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
@@ -0,0 +1,68 @@
+/*
+ * 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.wm.utils;
+
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * TestActivity that will ensure it dismisses keyguard and shows as a fullscreen activity.
+ */
+public class TestActivity extends Activity {
+    private static final int sTypeMask = systemBars() | displayCutout();
+    private FrameLayout mParentLayout;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mParentLayout = new FrameLayout(this);
+        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT);
+        setContentView(mParentLayout, layoutParams);
+
+        WindowInsetsController windowInsetsController = getWindow().getInsetsController();
+        windowInsetsController.hide(sTypeMask);
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        getWindow().setAttributes(params);
+        getWindow().setDecorFitsSystemWindows(false);
+
+        final KeyguardManager keyguardManager = getInstrumentation().getContext().getSystemService(
+                KeyguardManager.class);
+        if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+            keyguardManager.requestDismissKeyguard(this, null);
+        }
+    }
+
+    public FrameLayout getParentLayout() {
+        return mParentLayout;
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index bfb159f..dce4818 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -31,6 +31,7 @@
 import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
 import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
 import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
@@ -42,7 +43,9 @@
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -575,6 +578,23 @@
                         continue;
                     }
                     break;
+                case USER_INTERACTION:
+                    if (event.mUserInteractionExtrasToken != null) {
+                        String category = packagesTokenData.getString(packageToken,
+                                event.mUserInteractionExtrasToken.mCategoryToken);
+                        String action = packagesTokenData.getString(packageToken,
+                                event.mUserInteractionExtrasToken.mActionToken);
+                        if (TextUtils.isEmpty(category) || TextUtils.isEmpty(action)) {
+                            this.events.remove(i);
+                            dataOmitted = true;
+                            continue;
+                        }
+                        event.mExtras = new PersistableBundle();
+                        event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, category);
+                        event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action);
+                        event.mUserInteractionExtrasToken = null;
+                    }
+                    break;
             }
         }
         if (dataOmitted) {
@@ -692,13 +712,30 @@
                                 event.mPackage, event.mLocusId);
                     }
                     break;
+                case USER_INTERACTION:
+                    if (event.mExtras != null && event.mExtras.size() != 0) {
+                        final String category = event.mExtras.getString(
+                                UsageStatsManager.EXTRA_EVENT_CATEGORY);
+                        final String action = event.mExtras.getString(
+                                UsageStatsManager.EXTRA_EVENT_ACTION);
+                        if (!TextUtils.isEmpty(category) && !TextUtils.isEmpty(action)) {
+                            event.mUserInteractionExtrasToken =
+                                    new Event.UserInteractionEventExtrasToken();
+                            event.mUserInteractionExtrasToken.mCategoryToken =
+                                    packagesTokenData.getTokenOrAdd(packageToken, event.mPackage,
+                                            category);
+                            event.mUserInteractionExtrasToken.mActionToken =
+                                    packagesTokenData.getTokenOrAdd(packageToken, event.mPackage,
+                                            action);
+                        }
+                    }
+                    break;
             }
         }
     }
 
     /**
      * Obfuscates the data in this instance of interval stats.
-     *
      * @hide
      */
     public void obfuscateData(PackagesTokenData packagesTokenData) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
index 8138747..d865345 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
@@ -17,8 +17,10 @@
 
 import android.app.usage.ConfigurationStats;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event.UserInteractionEventExtrasToken;
 import android.app.usage.UsageStats;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -26,6 +28,8 @@
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -282,6 +286,16 @@
                     event.mLocusIdToken = proto.readInt(
                             EventObfuscatedProto.LOCUS_ID_TOKEN) - 1;
                     break;
+                case (int) EventObfuscatedProto.INTERACTION_EXTRAS:
+                    try {
+                        final long interactionExtrasToken = proto.start(
+                                EventObfuscatedProto.INTERACTION_EXTRAS);
+                        event.mUserInteractionExtrasToken = parseUserInteractionEventExtras(proto);
+                        proto.end(interactionExtrasToken);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some user interaction extras from proto.", e);
+                    }
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event;
             }
@@ -386,7 +400,7 @@
     }
 
     private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
-            final UsageEvents.Event event) throws IllegalArgumentException {
+            final UsageEvents.Event event) throws IOException, IllegalArgumentException {
         proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
         if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
             proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
@@ -429,6 +443,12 @@
                             event.mNotificationChannelIdToken + 1);
                 }
                 break;
+            case UsageEvents.Event.USER_INTERACTION:
+                if (event.mUserInteractionExtrasToken != null) {
+                    writeUserInteractionEventExtras(proto, EventObfuscatedProto.INTERACTION_EXTRAS,
+                            event.mUserInteractionExtrasToken);
+                }
+                break;
         }
     }
 
@@ -703,6 +723,9 @@
                 case (int) PendingEventProto.TASK_ROOT_CLASS:
                     event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS);
                     break;
+                case (int) PendingEventProto.EXTRAS:
+                    event.mExtras = parsePendingEventExtras(proto, PendingEventProto.EXTRAS);
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     // Handle default values for certain events types
                     switch (event.mEventType) {
@@ -757,7 +780,7 @@
     }
 
     private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)
-            throws IllegalArgumentException {
+            throws IOException, IllegalArgumentException {
         proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
         if (event.mClass != null) {
             proto.write(PendingEventProto.CLASS_NAME, event.mClass);
@@ -794,6 +817,11 @@
                             event.mNotificationChannelId);
                 }
                 break;
+            case UsageEvents.Event.USER_INTERACTION:
+                if (event.mExtras != null && event.mExtras.size() != 0) {
+                    writePendingEventExtras(proto, PendingEventProto.EXTRAS, event.mExtras);
+                }
+                break;
         }
     }
 
@@ -888,4 +916,52 @@
             proto.end(token);
         }
     }
+
+    private static UserInteractionEventExtrasToken parseUserInteractionEventExtras(
+            ProtoInputStream proto) throws IOException {
+        UserInteractionEventExtrasToken interactionExtrasToken =
+                new UserInteractionEventExtrasToken();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN:
+                    interactionExtrasToken.mCategoryToken = proto.readInt(
+                            ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN) - 1;
+                    break;
+                case (int) ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN:
+                    interactionExtrasToken.mActionToken = proto.readInt(
+                            ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN) - 1;
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return interactionExtrasToken;
+            }
+        }
+    }
+
+    static void writeUserInteractionEventExtras(ProtoOutputStream proto, long fieldId,
+            UserInteractionEventExtrasToken interactionExtras) {
+        final long token = proto.start(fieldId);
+        proto.write(ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN,
+                interactionExtras.mCategoryToken + 1);
+        proto.write(ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN,
+                interactionExtras.mActionToken + 1);
+        proto.end(token);
+    }
+
+    /**
+     * Populates the extra details for pending interaction event from the protobuf stream.
+     */
+    private static PersistableBundle parsePendingEventExtras(ProtoInputStream proto, long fieldId)
+            throws IOException {
+        return PersistableBundle.readFromStream(new ByteArrayInputStream(proto.readBytes(fieldId)));
+    }
+
+    /**
+     * Write the extra details for pending interaction event to a protobuf stream.
+     */
+    static void writePendingEventExtras(ProtoOutputStream proto, long fieldId,
+            PersistableBundle eventExtras) throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        eventExtras.writeToStream(baos);
+        proto.write(fieldId, baos.toByteArray());
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 63de41f..0e1e0c8 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -83,6 +83,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -91,6 +92,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -193,6 +195,11 @@
 
     private static final char TOKEN_DELIMITER = '/';
 
+    // The maximum length for extras {@link UsageStatsManager#EXTRA_EVENT_CATEGORY},
+    // {@link UsageStatsManager#EXTRA_EVENT_ACTION} in a {@link UsageEvents.Event#mExtras}.
+    // The value will be truncated at this limit.
+    private static final int MAX_TEXT_LENGTH = 127;
+
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
@@ -1814,6 +1821,13 @@
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
+    private String getTrimmedString(String input) {
+        if (input != null && input.length() > MAX_TEXT_LENGTH) {
+            return input.substring(0, MAX_TEXT_LENGTH);
+        }
+        return input;
+    }
+
     /**
      * Called by the Binder stub.
      */
@@ -1953,6 +1967,13 @@
             }
         }
 
+        // Flags status.
+        pw.println("Flags:");
+        pw.println("    " + Flags.FLAG_USER_INTERACTION_TYPE_API
+                + ": " + Flags.userInteractionTypeApi());
+        pw.println("    " + Flags.FLAG_USE_PARCELED_LIST
+                + ": " + Flags.useParceledList());
+
         final int[] userIds;
         synchronized (mLock) {
             final int userCount = mUserState.size();
@@ -2246,6 +2267,32 @@
             }
         }
 
+        private void reportUserInteractionInnerHelper(String packageName, @UserIdInt int userId,
+                PersistableBundle extras) {
+            if (Flags.reportUsageStatsPermission()) {
+                if (!canReportUsageStats()) {
+                    throw new SecurityException(
+                        "Only the system or holders of the REPORT_USAGE_STATS"
+                            + " permission are allowed to call reportUserInteraction");
+                }
+            } else {
+                if (!isCallingUidSystem()) {
+                    throw new SecurityException("Only system is allowed to call"
+                        + " reportUserInteraction");
+                }
+            }
+
+            // Verify if this package exists before reporting an event for it.
+            if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+                throw new IllegalArgumentException("Package " + packageName + "not exist!");
+            }
+
+            final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
+            event.mPackage = packageName;
+            event.mExtras = extras;
+            reportEventOrAddToQueue(userId, event);
+        }
+
         @Override
         public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
                 long endTime, String callingPackage, int userId) {
@@ -2679,23 +2726,36 @@
 
         @Override
         public void reportUserInteraction(String packageName, int userId) {
+            reportUserInteractionInnerHelper(packageName, userId, null);
+        }
+
+        @Override
+        public void reportUserInteractionWithBundle(String packageName, @UserIdInt int userId,
+                PersistableBundle extras) {
             Objects.requireNonNull(packageName);
-            if (Flags.reportUsageStatsPermission()) {
-                if (!canReportUsageStats()) {
-                    throw new SecurityException(
-                        "Only the system or holders of the REPORT_USAGE_STATS"
-                            + " permission are allowed to call reportUserInteraction");
-                }
-            } else {
-                if (!isCallingUidSystem()) {
-                    throw new SecurityException("Only system is allowed to call"
-                            + " reportUserInteraction");
-                }
+            if (extras == null || extras.size() == 0) {
+                throw new IllegalArgumentException("Emtry extras!");
             }
 
-            final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
-            event.mPackage = packageName;
-            reportEventOrAddToQueue(userId, event);
+            // Only category/action are allowed now, other unknown keys will be trimmed.
+            // Also, empty category/action is not meanful.
+            String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY);
+            if (TextUtils.isEmpty(category)) {
+                throw new IllegalArgumentException("Empty "
+                        + UsageStatsManager.EXTRA_EVENT_CATEGORY);
+            }
+            String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION);
+            if (TextUtils.isEmpty(action)) {
+                throw new IllegalArgumentException("Empty "
+                        + UsageStatsManager.EXTRA_EVENT_ACTION);
+            }
+
+            PersistableBundle extrasCopy = new PersistableBundle();
+            extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                    getTrimmedString(category));
+            extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action));
+
+            reportUserInteractionInnerHelper(packageName, userId, extrasCopy);
         }
 
         @Override
@@ -3153,6 +3213,24 @@
         }
 
         @Override
+        public void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId,
+                @NonNull PersistableBundle extras) {
+            if (extras != null && extras.size() != 0) {
+                // Truncate the value if necessary.
+                String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY);
+                String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION);
+                extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                        getTrimmedString(category));
+                extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action));
+            }
+
+            Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
+            event.mPackage = pkgName;
+            event.mExtras = extras;
+            reportEventOrAddToQueue(userId, event);
+        }
+
+        @Override
         public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
             return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
                     userId, SystemClock.elapsedRealtime());
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 9b67ab6..3bc7752 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -1110,6 +1110,10 @@
         if (event.mNotificationChannelId != null) {
             pw.printPair("channelId", event.mNotificationChannelId);
         }
+
+        if ((event.mEventType == Event.USER_INTERACTION) && (event.mExtras != null)) {
+            pw.print(event.mExtras.toString());
+        }
         pw.printHexPair("flags", event.mFlags);
         pw.println();
     }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 2975e1e..fc7c6a6 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -1231,6 +1231,26 @@
                     complianceWarningsProto.add(FrameworkStatsLog
                         .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_MISSING_RP);
                     continue;
+                case UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED:
+                    complianceWarningsProto.add(FrameworkStatsLog
+                        .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_INPUT_POWER_LIMITED);
+                    continue;
+                case UsbPortStatus.COMPLIANCE_WARNING_MISSING_DATA_LINES:
+                    complianceWarningsProto.add(FrameworkStatsLog
+                        .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_MISSING_DATA_LINES);
+                    continue;
+                case UsbPortStatus.COMPLIANCE_WARNING_ENUMERATION_FAIL:
+                    complianceWarningsProto.add(FrameworkStatsLog
+                        .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_ENUMERATION_FAIL);
+                    continue;
+                case UsbPortStatus.COMPLIANCE_WARNING_FLAKY_CONNECTION:
+                    complianceWarningsProto.add(FrameworkStatsLog
+                        .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_FLAKY_CONNECTION);
+                    continue;
+                case UsbPortStatus.COMPLIANCE_WARNING_UNRELIABLE_IO:
+                    complianceWarningsProto.add(FrameworkStatsLog
+                        .USB_COMPLIANCE_WARNINGS_REPORTED__COMPLIANCE_WARNINGS__COMPLIANCE_WARNING_UNRELIABLE_IO);
+                    continue;
             }
         }
         return complianceWarningsProto.toArray();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a584fc9..7239ba9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -24,6 +24,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
@@ -1565,6 +1566,33 @@
             }
         }
 
+        @Override
+        public boolean setSandboxedDetectionTrainingDataOp(int opMode) {
+            synchronized (this) {
+                enforceIsCallerPreinstalledAssistant();
+
+                if (mImpl == null) {
+                    Slog.w(TAG, "setSandboxedDetectionTrainingDataop without running"
+                            + " voice interaction service");
+                    return false;
+                }
+
+                int callingUid = Binder.getCallingUid();
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    AppOpsManager appOpsManager = (AppOpsManager)
+                            mContext.getSystemService(Context.APP_OPS_SERVICE);
+                    appOpsManager.setUidMode(
+                            AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                            callingUid, opMode);
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+
+                return true;
+            }
+        }
+
       //----------------- Model management APIs --------------------------------//
 
         @Override
@@ -2219,6 +2247,13 @@
             }
         }
 
+        private void enforceIsCallerPreinstalledAssistant() {
+            if (!isCallerPreinstalledAssistant()) {
+                throw new
+                        SecurityException("Caller is not the pre-installed assistant.");
+            }
+        }
+
         private void enforceCallerAllowedToEnrollVoiceModel() {
             if (isCallerHoldingPermission(Manifest.permission.KEYPHRASE_ENROLLMENT_APPLICATION)) {
                 return;
@@ -2233,6 +2268,13 @@
                     && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid();
         }
 
+        private boolean isCallerPreinstalledAssistant() {
+            return mImpl != null
+                    && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid()
+                    && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp()
+                    || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp());
+        }
+
         private void setImplLocked(VoiceInteractionManagerServiceImpl impl) {
             mImpl = impl;
             mAtmInternal.notifyActiveVoiceInteractionServiceChanged(
@@ -2437,8 +2479,6 @@
                     synchronized (VoiceInteractionManagerServiceStub.this) {
                         Slog.i(TAG, "Force stopping current voice recognizer: "
                                 + getCurRecognizer(userHandle));
-                        // TODO: Figure out why the interactor was being cleared and document it.
-                        setCurInteractor(null, userHandle);
                         initRecognizer(userHandle);
                     }
                 }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index f3dfcd7..bbd01d6 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -289,6 +289,11 @@
 
             switch (msg.what) {
                 case MSG_SET_IN_CALL_ADAPTER:
+                    if (mPhone != null) {
+                        Log.i(this, "mPhone is already instantiated, ignoring "
+                                + "request to reset adapter.");
+                        break;
+                    }
                     String callingPackage = getApplicationContext().getOpPackageName();
                     mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
                             getApplicationContext().getApplicationInfo().targetSdkVersion);
@@ -384,14 +389,8 @@
 
     /** Manages the binder calls so that the implementor does not need to deal with it. */
     private final class InCallServiceBinder extends IInCallService.Stub {
-        private boolean mInCallAdapterSet;
         @Override
         public void setInCallAdapter(IInCallAdapter inCallAdapter) {
-            if (mInCallAdapterSet) {
-                Log.i(this, "setInCallAdapter: InCallAdapter already set, skipping...");
-                return;
-            }
-            mInCallAdapterSet = true;
             mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();
         }
 
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 55fecfc..b167f1b 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -228,6 +228,14 @@
             "android.telecom.extra.DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME";
 
     /**
+     * Optional extra to indicate a call should not be added to the call log.
+     *
+     * @hide
+     */
+    public static final String EXTRA_DO_NOT_LOG_CALL =
+            "android.telecom.extra.DO_NOT_LOG_CALL";
+
+    /**
      * Extra value used with {@link #ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED} broadcast to
      * indicate whether an app is the default call screening app.
      * <p>
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index f7c8237..cc8a992 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -43,7 +43,11 @@
 @SystemApi
 public final class EuiccProfileInfo implements Parcelable {
 
-    /** Profile policy rules (bit mask) */
+    /**
+     * Profile policy rules (bit mask)
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "POLICY_RULE_" }, value = {
             POLICY_RULE_DO_NOT_DISABLE,
@@ -58,7 +62,11 @@
     /** This profile should be deleted after being disabled. */
     public static final int POLICY_RULE_DELETE_AFTER_DISABLING = 1 << 2;
 
-    /** Class of the profile */
+    /**
+     * Class of the profile
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
             PROFILE_CLASS_TESTING,
@@ -79,7 +87,11 @@
      */
     public static final int PROFILE_CLASS_UNSET = -1;
 
-    /** State of the profile */
+    /**
+     * State of the profile
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "PROFILE_STATE_" }, value = {
             PROFILE_STATE_DISABLED,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c124079..ede4885 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8853,6 +8853,24 @@
                 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.
+         */
+        @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.
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
+        public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
+                KEY_PREFIX + "supports_child_session_multiple_sa_proposals_bool";
+
+        /**
          * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of child
          * session. Possible values are:
          * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED},
@@ -9187,6 +9205,8 @@
             defaults.putInt(KEY_IKE_REKEY_HARD_TIMER_SEC_INT, 14400);
             defaults.putInt(KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT, 3600);
             defaults.putInt(KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT, 7200);
+            defaults.putBoolean(KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false);
+            defaults.putBoolean(KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false);
             defaults.putIntArray(
                     KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY, new int[] {500, 1000, 2000, 4000, 8000});
             defaults.putInt(KEY_DPD_TIMER_SEC_INT, 120);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f8608b8..e12a815 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -23,6 +23,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,6 +71,7 @@
 import com.android.internal.telephony.ISetOpportunisticDataCallback;
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.HandlerExecutor;
 import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.Preconditions;
@@ -95,7 +97,22 @@
 import java.util.stream.Collectors;
 
 /**
- * Subscription manager provides the mobile subscription information.
+ * Subscription manager provides the mobile subscription information that are associated with the
+ * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ *     <li> Subscription 1 associated with personal profile.
+ *     <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then
+ * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
+ * may be required as documented on the each API.
+ *
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1446,6 +1463,16 @@
         }
     }
 
+    /**
+     * {@code true} if the SubscriptionManager instance should see all subscriptions regardless its
+     * association with particular user profile.
+     *
+     * <p> It only applies to Android SDK 35(V) and above. For Android SDK 34(U) and below, the
+     * caller can see all subscription across user profiles as it does today today even if it's
+     * {@code false}.
+     */
+    private boolean mIsForAllUserProfiles = false;
+
     /** @hide */
     @UnsupportedAppUsage
     public SubscriptionManager(Context context) {
@@ -1776,8 +1803,23 @@
     }
 
     /**
-     * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
-     * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
+     * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
+     * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+     * and below can see all subscriptions as it does today.
+     *
+     * <p>For example, if we have
+     * <ul>
+     *     <li> Subscription 1 associated with personal profile.
+     *     <li> Subscription 2 associated with work profile.
+     * </ul>
+     * Then for SDK 35+, if the caller identity is personal profile, then this will return
+     * subscription 1 only and vice versa.
+     *
+     * <p>If the caller needs to see all subscriptions across user profiles,
+     * use {@link #createForAllUserProfiles} to convert this instance to see all.
+     *
+     * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+     * {@link SubscriptionInfo#getSubscriptionId}.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
@@ -1800,8 +1842,25 @@
      * </ul>
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    // @RequiresPermission(TODO(b/308809058))
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
-        return getActiveSubscriptionInfoList(/* userVisibleonly */true);
+        List<SubscriptionInfo> activeList = null;
+
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag(), mIsForAllUserProfiles);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (activeList != null) {
+            activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
+                    .collect(Collectors.toList());
+        }
+        return activeList;
     }
 
     /**
@@ -1835,6 +1894,26 @@
     }
 
     /**
+     * Convert this subscription manager instance into one that can see all subscriptions across
+     * user profiles.
+     *
+     * @return a SubscriptionManager that can see all subscriptions regardless its user profile
+     * association.
+     *
+     * @see #getActiveSubscriptionInfoList
+     * @see #getActiveSubscriptionInfoCount
+     * @see UserHandle
+     */
+    @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+    // @RequiresPermission(TODO(b/308809058))
+    // The permission check for accessing all subscriptions will be enforced upon calling the
+    // individual APIs linked above.
+    @NonNull public SubscriptionManager createForAllUserProfiles() {
+        mIsForAllUserProfiles = true;
+        return this;
+    }
+
+    /**
     * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
     * is true, it will filter out the hidden subscriptions.
     *
@@ -1847,7 +1926,7 @@
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
                 activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), true /*isForAllUserProfiles*/);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -2002,13 +2081,19 @@
     }
 
     /**
-     * Get the active subscription count.
+     * Get the active subscription count associated with the current caller user profile for
+     * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
+     * it does today.
+     *
+     * <p>If the caller needs to see all subscriptions across user profiles,
+     * use {@link #createForAllUserProfiles} to convert this instance to see all.
      *
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    // @RequiresPermission(TODO(b/308809058))
     public int getActiveSubscriptionInfoCount() {
         int result = 0;
 
@@ -2016,7 +2101,7 @@
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), mIsForAllUserProfiles);
             }
         } catch (RemoteException ex) {
             // ignore it
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 611f97b..e981e1f 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -67,7 +67,11 @@
 public class EuiccCardManager {
     private static final String TAG = "EuiccCardManager";
 
-    /** Reason for canceling a profile download session */
+    /**
+     * Reason for canceling a profile download session
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CANCEL_REASON_"}, value = {
             CANCEL_REASON_END_USER_REJECTED,
@@ -97,7 +101,11 @@
      */
     public static final int CANCEL_REASON_PPR_NOT_ALLOWED = 3;
 
-    /** Options for resetting eUICC memory */
+    /**
+     * Options for resetting eUICC memory
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = {"RESET_OPTION_"}, value = {
             RESET_OPTION_DELETE_OPERATIONAL_PROFILES,
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index b9a7d43..86fbb04 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -552,9 +552,8 @@
 
     /**
      * Euicc OTA update status which can be got by {@link #getOtaStatus}
-     * @hide
+     * @removed mistakenly exposed as system-api previously
      */
-    @SystemApi
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"EUICC_OTA_"}, value = {
             EUICC_OTA_IN_PROGRESS,
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index be0048f..fcc0b6a 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -36,7 +36,11 @@
  */
 @SystemApi
 public final class EuiccNotification implements Parcelable {
-    /** Event */
+    /**
+     * Event
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "EVENT_" }, value = {
             EVENT_INSTALL,
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index 1c6b6b6..c35242d 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -37,7 +37,11 @@
  */
 @SystemApi
 public final class EuiccRulesAuthTable implements Parcelable {
-    /** Profile policy rule flags */
+    /**
+     * Profile policy rule flags
+     *
+     * @removed mistakenly exposed previously
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
             POLICY_RULE_FLAG_CONSENT_REQUIRED
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index d2dbeb7..3f41d56 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -66,6 +66,8 @@
      *
      * @param callingPackage The package maing the call.
      * @param callingFeatureId The feature in the package
+     * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+     *                      association.
      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
      * <ul>
      * <li>
@@ -83,14 +85,17 @@
      * </ul>
      */
     List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
-            String callingFeatureId);
+            String callingFeatureId, boolean isForAllProfiles);
 
     /**
      * @param callingPackage The package making the call.
      * @param callingFeatureId The feature in the package.
+     * @param isForAllProfile whether the caller intends to see all subscriptions regardless
+     *                      association.
      * @return the number of active subscriptions
      */
-    int getActiveSubInfoCount(String callingPackage, String callingFeatureId);
+    int getActiveSubInfoCount(String callingPackage, String callingFeatureId,
+            boolean isForAllProfile);
 
     /**
      * @return the maximum number of subscriptions this device will support at any one time.
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index edd6597..5059017 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,8 @@
     int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;
     int RIL_REQUEST_IS_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED = 246;
     int RIL_REQUEST_SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED = 247;
+    int RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED = 248;
+    int RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED = 249;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -608,6 +610,7 @@
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
     int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
     int RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED = 1056;
+    int RIL_UNSOL_SECURITY_ALGORITHMS_UPDATED = 1057;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 4548a7d..1e5f33f 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -710,9 +710,15 @@
             float childFrameRate = Collections.max(frameRates);
             float parentFrameRate = childFrameRate / 2;
             int initialNumEvents = mModeChangedEvents.size();
-            parent.setFrameRate(parentFrameRate);
             parent.setFrameRateSelectionStrategy(parentStrategy);
-            child.setFrameRate(childFrameRate);
+
+            // For Self case, we want to test that child gets default behavior
+            if (parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF) {
+                parent.setFrameRateCategory(Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            } else {
+                parent.setFrameRate(parentFrameRate);
+                child.setFrameRate(childFrameRate);
+            }
 
             // Verify
             float expectedFrameRate =
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index bed9cff..29f6879 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -16,11 +16,8 @@
 
 package android.view.surfacecontroltests;
 
-import static org.junit.Assume.assumeTrue;
-
 import android.Manifest;
 import android.hardware.display.DisplayManager;
-import android.os.SystemProperties;
 import android.support.test.uiautomator.UiDevice;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -54,10 +51,6 @@
 
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
-        // TODO(b/290634611): clean this up once SF new front end is enabled by default
-        assumeTrue(SystemProperties.getBoolean(
-                "persist.debug.sf.enable_layer_lifecycle_manager", false));
-
         uiDevice.wakeUp();
         uiDevice.executeShellCommand("wm dismiss-keyguard");
 
@@ -118,10 +111,11 @@
     }
 
     @Test
-    public void testSurfaceControlFrameRateSelectionStrategySelf() throws InterruptedException {
+    public void testSurfaceControlFrameRateSelectionStrategyPropagate()
+            throws InterruptedException {
         GraphicsActivity activity = mActivityRule.getActivity();
         activity.testSurfaceControlFrameRateSelectionStrategy(
-                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF);
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
     }
 
     @Test
@@ -131,4 +125,12 @@
         activity.testSurfaceControlFrameRateSelectionStrategy(
                 SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
+
+    @Test
+    public void testSurfaceControlFrameRateSelectionStrategySelf()
+            throws InterruptedException {
+        GraphicsActivity activity = mActivityRule.getActivity();
+        activity.testSurfaceControlFrameRateSelectionStrategy(
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF);
+    }
 }
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 9c2899ac..3aee932 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -20,6 +20,8 @@
 import android.app.WallpaperManager
 import android.content.res.Resources
 import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
 import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.ComponentNameMatcher.Companion.SPLASH_SCREEN
@@ -125,27 +127,19 @@
         val backgroundColorLayer = ComponentNameMatcher("", "animation-background")
         val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
         flicker.assertLayers {
-            this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
-                }
+            visibleRegionCovers(launchNewTaskApp.componentMatcher, displayBounds)
                 .isInvisible(backgroundColorLayer)
-                .hasNoColor(backgroundColorLayer)
                 .then()
                 // Transitioning
                 .isVisible(backgroundColorLayer)
-                .hasColor(backgroundColorLayer)
                 .then()
                 // Fully transitioned to simple SIMPLE_ACTIVITY
-                .invoke(
-                    "SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds",
+                .visibleRegionCovers(
+                    ComponentSplashScreenMatcher(simpleApp.componentMatcher),
+                    displayBounds,
                     isOptional = true
-                ) {
-                    it.visibleRegion(ComponentSplashScreenMatcher(simpleApp.componentMatcher))
-                        .coversExactly(displayBounds)
-                }
-                .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds)
-                }
+                )
+                .visibleRegionCovers(simpleApp.componentMatcher, displayBounds)
                 .isInvisible(backgroundColorLayer)
                 .hasNoColor(backgroundColorLayer)
                 .then()
@@ -154,18 +148,12 @@
                 .hasColor(backgroundColorLayer)
                 .then()
                 // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
-                .invoke(
-                    "LAUNCH_NEW_TASK_ACTIVITY's splashscreen coversExactly displayBounds",
+                .visibleRegionCovers(
+                    ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher),
+                    displayBounds,
                     isOptional = true
-                ) {
-                    it.visibleRegion(
-                            ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher)
-                        )
-                        .coversExactly(displayBounds)
-                }
-                .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
-                }
+                )
+                .visibleRegionCovers(launchNewTaskApp.componentMatcher, displayBounds)
                 .isInvisible(backgroundColorLayer)
                 .hasNoColor(backgroundColorLayer)
         }
@@ -223,6 +211,14 @@
             return ComponentNameMatcher(rawComponentMatcher.className)
         }
 
+        private fun LayersTraceSubject.visibleRegionCovers(
+            component: IComponentMatcher,
+            expectedArea: Region,
+            isOptional: Boolean = true
+        ): LayersTraceSubject = invoke("$component coversExactly $expectedArea", isOptional) {
+            it.visibleRegion(component).coversExactly(expectedArea)
+        }
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index b44f1a6..c49f8fe 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -23,10 +23,13 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,7 +56,12 @@
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
         }
-        transitions { testApp.finishActivity(wmHelper) }
+        transitions {
+            broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
+            wmHelper.StateSyncBuilder()
+                    .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+                    .waitForAndVerify()
+        }
         teardown { simpleApp.exit(wmHelper) }
     }
 
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 976ac82..994edc5 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,7 +54,11 @@
             // Enable letterbox when the app calls setRequestedOrientation
             device.executeShellCommand("cmd window set-ignore-orientation-request true")
         }
-        transitions { testApp.toggleFixPortraitOrientation(wmHelper) }
+        transitions {
+            broadcastActionTrigger.doAction(ACTION_TOGGLE_ORIENTATION)
+            // Ensure app relaunching transition finished and the IME was shown
+            testApp.waitIMEShown(wmHelper)
+        }
         teardown {
             testApp.exit()
             device.executeShellCommand("cmd window set-ignore-orientation-request false")
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 05babd67..b7a183d 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -23,7 +23,6 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -46,6 +45,7 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
+            tapl.expectedRotationCheckEnabled = false
             tapl.workspace.switchToOverview().dismissAllTasks()
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
@@ -54,7 +54,7 @@
             wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
         }
         transitions {
-            device.reopenAppFromOverview(wmHelper)
+            tapl.overview.currentTask.open()
             wmHelper.StateSyncBuilder().withFullScreenApp(testApp).withImeShown().waitForAndVerify()
         }
         teardown { testApp.exit(wmHelper) }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index aff8e65..6ee5a9a 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -104,7 +104,7 @@
 
     @Presubmit
     @Test
-    open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
+    fun imeLayerIsVisibleWhenSwitchingToImeApp() {
         flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
         flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
         flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 4ffdcea..1ad5c0d 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -93,7 +93,7 @@
         }
         transitions {
             testApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
+            testApp.waitIMEShown(wmHelper)
         }
     }
 
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 6ad235c..181a2a2 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -23,11 +23,14 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
 import android.view.WindowInsets.Type.ime
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.FixMethodOrder
@@ -50,8 +53,12 @@
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
             testApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
-            testApp.startDialogThemedActivity(wmHelper)
+            testApp.waitIMEShown(wmHelper)
+            broadcastActionTrigger.doAction(ACTION_START_DIALOG_THEMED_ACTIVITY)
+            wmHelper.StateSyncBuilder()
+                    .withFullScreenApp(
+                        ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
+                    .waitForAndVerify()
             // Verify IME insets isn't visible on dialog since it's non-IME focusable window
             assertFalse(testApp.getInsetsVisibleFromDialog(ime()))
             assertTrue(testApp.getInsetsVisibleFromDialog(statusBars()))
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 7c9c05d..ad272a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker
 
 import android.app.Instrumentation
+import android.content.Intent
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerBuilderProvider
@@ -50,6 +51,19 @@
     /** Specification of the test transition to execute */
     abstract val transition: FlickerBuilder.() -> Unit
 
+    protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
+
+    // Helper class to process test actions by broadcast.
+    protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
+        private fun createIntentWithAction(broadcastAction: String): Intent {
+            return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        }
+
+        fun doAction(broadcastAction: String) {
+            instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction))
+        }
+    }
+
     /**
      * Entry point for the test runner. It will use this method to initialize and cache flicker
      * executions
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 252f7d3..cb1aab0 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -50,7 +50,7 @@
         waitIMEShown(wmHelper)
     }
 
-    protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
+    fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
         wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
     }
 
@@ -63,17 +63,4 @@
         uiDevice.pressBack()
         wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify()
     }
-
-    open fun finishActivity(wmHelper: WindowManagerStateHelper) {
-        val finishButton =
-            uiDevice.wait(
-                Until.findObject(By.res(packageName, "finish_activity_btn")),
-                FIND_TIMEOUT
-            )
-        requireNotNull(finishButton) {
-            "Finish activity button not found, probably IME activity is not on the screen?"
-        }
-        finishButton.click()
-        wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify()
-    }
 }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index d3cee64..0ee7aee 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -74,24 +74,6 @@
         open(expectedPackage)
     }
 
-    fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
-        val button =
-            uiDevice.wait(
-                Until.findObject(By.res(packageName, "start_dialog_themed_activity_btn")),
-                FIND_TIMEOUT
-            )
-
-        requireNotNull(button) {
-            "Button not found, this usually happens when the device " +
-                "was left in an unknown state (e.g. Screen turned off)"
-        }
-        button.click()
-        wmHelper
-            .StateSyncBuilder()
-            .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
-            .waitForAndVerify()
-    }
-
     fun dismissDialog(wmHelper: WindowManagerStateHelper) {
         val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
 
@@ -126,20 +108,4 @@
         }
         return false
     }
-
-    fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
-        val button =
-            uiDevice.wait(
-                Until.findObject(By.res(packageName, "toggle_fixed_portrait_btn")),
-                FIND_TIMEOUT
-            )
-        require(button != null) {
-            "Button not found, this usually happens when the device " +
-                "was left in an unknown state (e.g. Screen turned off)"
-        }
-        button.click()
-        instrumentation.waitForIdleSync()
-        // Ensure app relaunching transition finish and the IME has shown
-        waitIMEShown(wmHelper)
-    }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index fa73e2c..507c1b6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
  Copyright 2018 The Android Open Source Project
 
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,39 +13,17 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
+    android:background="@android:color/holo_green_light"
     android:focusableInTouchMode="true"
-    android:background="@android:color/holo_green_light">
-    <EditText android:id="@+id/plain_text_input"
-              android:layout_height="wrap_content"
-              android:layout_width="match_parent"
-	      android:imeOptions="flagNoExtractUi"
-              android:inputType="text"/>
-    <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical">
+
+    <EditText
+        android:id="@+id/plain_text_input"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal">
-        <Button
-            android:id="@+id/finish_activity_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Finish activity" />
-        <Button
-            android:id="@+id/start_dialog_themed_activity_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Dialog themed activity" />
-        <ToggleButton
-            android:id="@+id/toggle_fixed_portrait_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textOn="Portrait (On)"
-            android:textOff="Portrait (Off)"
-        />
-    </LinearLayout>
+        android:layout_height="wrap_content"
+        android:imeOptions="flagNoExtractUi"
+        android:inputType="text" />
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 8b334c0..80c1dd0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -40,6 +40,18 @@
             public static final String LABEL = "ImeActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeActivity");
+
+            /** Intent action used to finish the test activity. */
+            public static final String ACTION_FINISH_ACTIVITY =
+                    FLICKER_APP_PACKAGE + ".ImeActivity.FINISH_ACTIVITY";
+
+            /** Intent action used to start a {@link DialogThemedActivity}. */
+            public static final String ACTION_START_DIALOG_THEMED_ACTIVITY =
+                    FLICKER_APP_PACKAGE + ".ImeActivity.START_DIALOG_THEMED_ACTIVITY";
+
+            /** Intent action used to toggle activity orientation. */
+            public static final String ACTION_TOGGLE_ORIENTATION =
+                    FLICKER_APP_PACKAGE + ".ImeActivity.TOGGLE_ORIENTATION";
         }
 
         public static class AutoFocusActivity {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
index d7ee2af..4418b5a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
@@ -16,12 +16,51 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION;
+
 import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.WindowManager;
-import android.widget.Button;
 
 public class ImeActivity extends Activity {
+
+    private static final String TAG = "ImeActivity";
+
+    /** Receiver used to handle actions coming from the test helper methods. */
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case ACTION_FINISH_ACTIVITY -> finish();
+                case ACTION_START_DIALOG_THEMED_ACTIVITY -> startActivity(
+                        new Intent(context, DialogThemedActivity.class));
+                case ACTION_TOGGLE_ORIENTATION -> {
+                    mIsPortrait = !mIsPortrait;
+                    setRequestedOrientation(mIsPortrait
+                            ? SCREEN_ORIENTATION_PORTRAIT
+                            : SCREEN_ORIENTATION_UNSPECIFIED);
+                }
+                default -> Log.w(TAG, "Unhandled action=" + intent.getAction());
+            }
+        }
+    };
+
+    /**
+     * Used to toggle activity orientation between portrait when {@code true} and
+     * unspecified otherwise.
+     */
+    private boolean mIsPortrait = false;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -30,9 +69,17 @@
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         getWindow().setAttributes(p);
         setContentView(R.layout.activity_ime);
-        Button button = findViewById(R.id.finish_activity_btn);
-        button.setOnClickListener(view -> {
-            finish();
-        });
+
+        final var filter = new IntentFilter();
+        filter.addAction(ACTION_FINISH_ACTIVITY);
+        filter.addAction(ACTION_START_DIALOG_THEMED_ACTIVITY);
+        filter.addAction(ACTION_TOGGLE_ORIENTATION);
+        registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterReceiver(mBroadcastReceiver);
+        super.onDestroy();
     }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index 7ee8deb..cd711f7 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,29 +16,12 @@
 
 package com.android.server.wm.flicker.testapp;
 
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-
-import android.content.Intent;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ToggleButton;
-
 public class ImeActivityAutoFocus extends ImeActivity {
     @Override
     protected void onStart() {
         super.onStart();
 
-        Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
-        startThemedActivityButton.setOnClickListener(
-                button -> startActivity(new Intent(this, DialogThemedActivity.class)));
-
-        ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
-        toggleFixedPortraitButton.setOnCheckedChangeListener(
-                (button, isChecked) -> setRequestedOrientation(
-                        isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
-
-        EditText editTextField = findViewById(R.id.plain_text_input);
+        final var editTextField = findViewById(R.id.plain_text_input);
         editTextField.requestFocus();
     }
 }
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 93a5582..c1784f3 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -149,7 +149,9 @@
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
         verify(native).setStylusPointerIconEnabled(anyBoolean())
-        verify(native).setKeyRepeatConfiguration(anyInt(), anyInt())
+        // Called twice at boot, since there are individual callbacks to update the
+        // key repeat timeout and the key repeat delay.
+        verify(native, times(2)).setKeyRepeatConfiguration(anyInt(), anyInt())
     }
 
     @Test
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index d075b5f..5434c82 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -51,6 +51,7 @@
         assertEquals(device.getName(), outDevice.getName());
         assertEquals(device.getVendorId(), outDevice.getVendorId());
         assertEquals(device.getProductId(), outDevice.getProductId());
+        assertEquals(device.getDeviceBus(), outDevice.getDeviceBus());
         assertEquals(device.getDescriptor(), outDevice.getDescriptor());
         assertEquals(device.isExternal(), outDevice.isExternal());
         assertEquals(device.getSources(), outDevice.getSources());
@@ -79,6 +80,7 @@
                 .setName("Test Device " + DEVICE_ID)
                 .setVendorId(44)
                 .setProductId(45)
+                .setDeviceBus(3)
                 .setDescriptor("descriptor")
                 .setExternal(true)
                 .setSources(InputDevice.SOURCE_HDMI)
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
index 15aaa46..83ced2c 100644
--- a/tests/InputScreenshotTest/Android.bp
+++ b/tests/InputScreenshotTest/Android.bp
@@ -7,12 +7,27 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "InputScreenshotTestRNGFiles",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt",
+        "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt",
+    ],
+}
+
 android_test {
     name: "InputScreenshotTests",
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    exclude_srcs: [
+        "src/android/input/screenshot/package-info.java",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
@@ -43,6 +58,7 @@
         "hamcrest-library",
         "kotlin-test",
         "flag-junit",
+        "platform-parametric-runner-lib",
         "platform-test-annotations",
         "services.core.unboosted",
         "testables",
diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp
new file mode 100644
index 0000000..912f4b80
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/Android.bp
@@ -0,0 +1,71 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "InputRoboRNGTestsAssetsLib",
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    optimize: {
+        enabled: false,
+    },
+    use_resource_processor: true,
+}
+
+android_app {
+    name: "InputRoboApp",
+    srcs: [],
+    static_libs: [
+        "androidx.test.espresso.core",
+        "androidx.appcompat_appcompat",
+        "flag-junit",
+        "guava",
+        "InputRoboRNGTestsAssetsLib",
+        "platform-screenshot-diff-core",
+        "PlatformComposeSceneTransitionLayoutTestsUtils",
+    ],
+    manifest: "robo-manifest.xml",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.input.screenshot",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "InputRoboRNGTests",
+    srcs: [
+        ":InputScreenshotTestRNGFiles",
+        ":flag-junit",
+        ":platform-test-screenshot-rules",
+    ],
+    // Do not add any new libraries here, they should be added to SystemUIGoogleRobo above.
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "uiautomator-helpers",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth",
+    ],
+    upstream: true,
+    java_resource_dirs: ["config"],
+    instrumentation_for: "InputRoboApp",
+}
diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..5689311
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.input.screenshot">
+    <uses-sdk android:minSdkVersion="21"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 0000000..baf204a
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..deb3cee
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..34e25f7
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml
new file mode 100644
index 0000000..e86f58e
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.input.screenshot"
+          coreApp="true">
+  <application>
+    <activity
+        android:name="androidx.activity.ComponentActivity"
+        android:exported="true">
+    </activity>
+  </application>
+</manifest>
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index c2c3d55..75dab41 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.Bitmap
+import android.os.Build
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.Image
 import androidx.compose.ui.platform.ViewRootForTest
@@ -49,15 +50,17 @@
             )
         )
     private val composeRule = createAndroidComposeRule<ComponentActivity>()
-    private val delegateRule =
-            RuleChain.outerRule(colorsRule)
-                .around(deviceEmulationRule)
+    private val roboRule =
+            RuleChain.outerRule(deviceEmulationRule)
                 .around(screenshotRule)
                 .around(composeRule)
+    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
     private val matcher = UnitTestBitmapMatcher
+    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
+        val ruleToApply = if (isRobolectric) roboRule else delegateRule
+        return ruleToApply.apply(base, description)
     }
 
     /**
@@ -84,4 +87,4 @@
         val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
         screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index 8ae6dfd..ab7bb4e 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -26,14 +26,15 @@
 import org.junit.Test
 import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 
 /** A screenshot test for Keyboard layout preview for Iso physical layout. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
@@ -55,4 +56,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
new file mode 100644
index 0000000..4b5a56d
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
@@ -0,0 +1,4 @@
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.input.screenshot;
+
+import org.robolectric.annotation.GraphicsMode;
diff --git a/tests/SmokeTestApps/Android.bp b/tests/SmokeTestApps/Android.bp
index 3505fe1..38ee8ac 100644
--- a/tests/SmokeTestApps/Android.bp
+++ b/tests/SmokeTestApps/Android.bp
@@ -11,4 +11,7 @@
     name: "SmokeTestTriggerApps",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    errorprone: {
+        enabled: false,
+    },
 }
diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
index ff0fe32..af3789e 100644
--- a/tools/hoststubgen/hoststubgen/framework-policy-override.txt
+++ b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
@@ -78,6 +78,9 @@
 class com.android.internal.util.FastPrintWriter         keepclass
 class com.android.internal.util.LineBreakBufferedWriter keepclass
 
+class android.util.EventLog stubclass
+class android.util.EventLog !com.android.hoststubgen.nativesubstitution.EventLog_host
+class android.util.EventLog$Event stubclass
 
 # Expose Context because it's referred to by AndroidTestCase, but don't need to expose any of
 # its members.
@@ -97,3 +100,6 @@
 class android.os.BaseBundle        stubclass
 class android.os.Bundle            stubclass
 class android.os.PersistableBundle stubclass
+
+class android.os.MessageQueue stubclass
+class android.os.MessageQueue !com.android.hoststubgen.nativesubstitution.MessageQueue_host
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
new file mode 100644
index 0000000..292e8da
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
@@ -0,0 +1,69 @@
+/*
+ * 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.hoststubgen.nativesubstitution;
+
+import android.util.Log;
+import android.util.Log.Level;
+
+import java.util.Collection;
+
+public class EventLog_host {
+    public static int writeEvent(int tag, int value) {
+        return writeEvent(tag, (Object) value);
+    }
+
+    public static int writeEvent(int tag, long value) {
+        return writeEvent(tag, (Object) value);
+    }
+
+    public static int writeEvent(int tag, float value) {
+        return writeEvent(tag, (Object) value);
+    }
+
+    public static int writeEvent(int tag, String str) {
+        return writeEvent(tag, (Object) str);
+    }
+
+    public static int writeEvent(int tag, Object... list) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("logd: [event] ");
+        final String tagName = android.util.EventLog.getTagName(tag);
+        if (tagName != null) {
+            sb.append(tagName);
+        } else {
+            sb.append(tag);
+        }
+        sb.append(": [");
+        for (int i = 0; i < list.length; i++) {
+            sb.append(String.valueOf(list[i]));
+            if (i < list.length - 1) {
+                sb.append(',');
+            }
+        }
+        sb.append(']');
+        System.out.println(sb.toString());
+        return sb.length();
+    }
+
+    public static void readEvents(int[] tags, Collection<android.util.EventLog.Event> output) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static void readEventsOnWrapping(int[] tags, long timestamp,
+            Collection<android.util.EventLog.Event> output) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
new file mode 100644
index 0000000..2e47d48
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hoststubgen.nativesubstitution;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MessageQueue_host {
+    private static final AtomicLong sNextId = new AtomicLong(1);
+    private static final Map<Long, MessageQueue_host> sInstances = new ConcurrentHashMap<>();
+
+    private boolean mDeleted = false;
+
+    private final Object mPoller = new Object();
+    private volatile boolean mPolling;
+
+    private void validate() {
+        if (mDeleted) {
+            // TODO: Put more info
+            throw new RuntimeException("MessageQueue already destroyed");
+        }
+    }
+
+    private static MessageQueue_host getInstance(long id) {
+        MessageQueue_host q = sInstances.get(id);
+        if (q == null) {
+            throw new RuntimeException("MessageQueue doesn't exist with id=" + id);
+        }
+        q.validate();
+        return q;
+    }
+
+    public static long nativeInit() {
+        final long id = sNextId.getAndIncrement();
+        final MessageQueue_host q = new MessageQueue_host();
+        sInstances.put(id, q);
+        return id;
+    }
+
+    public static void nativeDestroy(long ptr) {
+        getInstance(ptr).mDeleted = true;
+        sInstances.remove(ptr);
+    }
+
+    public static void nativePollOnce(android.os.MessageQueue queue, long ptr, int timeoutMillis) {
+        var q = getInstance(ptr);
+        synchronized (q.mPoller) {
+            q.mPolling = true;
+            try {
+                if (timeoutMillis == 0) {
+                    // Calling epoll_wait() with 0 returns immediately
+                } else if (timeoutMillis == -1) {
+                    q.mPoller.wait();
+                } else {
+                    q.mPoller.wait(timeoutMillis);
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            q.mPolling = false;
+        }
+    }
+
+    public static void nativeWake(long ptr) {
+        var q = getInstance(ptr);
+        synchronized (q.mPoller) {
+            q.mPoller.notifyAll();
+        }
+    }
+
+    public static boolean nativeIsPolling(long ptr) {
+        var q = getInstance(ptr);
+        return q.mPolling;
+    }
+
+    public static void nativeSetFileDescriptorEvents(long ptr, int fd, int events) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index 4a3a798..2255345 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -27,16 +27,17 @@
 
 /**
  * Tentative, partial implementation of the Parcel native methods, using Java's
- * {@link ByteBuffer}. It turned out there's enough semantics differences between Parcel
- * and {@link ByteBuffer}, so it didn't actually work.
- * (e.g. Parcel seems to allow moving the data position to be beyond its size? Which
+ * {@code byte[]}.
+ * (We don't use a {@link ByteBuffer} because there's enough semantics differences between Parcel
+ * and {@link ByteBuffer}, and it didn't work out.
+ * e.g. Parcel seems to allow moving the data position to be beyond its size? Which
  * {@link ByteBuffer} wouldn't allow...)
  */
 public class Parcel_host {
     private Parcel_host() {
     }
 
-    private static final AtomicLong sNextId = new AtomicLong(0);
+    private static final AtomicLong sNextId = new AtomicLong(1);
 
     private static final Map<Long, Parcel_host> sInstances = new ConcurrentHashMap<>();
 
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index 89daa20..91e6814 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# This command is expected to be executed with: atest hoststubgen-invoke-test
+
 set -e # Exit when any command files
 
 # This script runs HostStubGen directly with various arguments and make sure
@@ -35,6 +37,12 @@
   mkdir -p $TEMP
 fi
 
+cleanup_temp() {
+  rm -fr $TEMP/*
+}
+
+cleanup_temp
+
 JAR=hoststubgen-test-tiny-framework.jar
 STUB=$TEMP/stub.jar
 IMPL=$TEMP/impl.jar
@@ -43,16 +51,16 @@
 
 HOSTSTUBGEN_OUT=$TEMP/output.txt
 
+EXTRA_ARGS=""
+
 # Because of `set -e`, we can't return non-zero from functions, so we store
 # HostStubGen result in it.
 HOSTSTUBGEN_RC=0
 
-# Define the functions to
-
-
 # Note, because the build rule will only install hoststubgen.jar, but not the wrapper script,
 # we need to execute it manually with the java command.
 hoststubgen() {
+  echo "Running hoststubgen with: $*"
   java -jar ./hoststubgen.jar "$@"
 }
 
@@ -62,7 +70,7 @@
 
   echo "# Test: $test_name"
 
-  rm -f $HOSTSTUBGEN_OUT
+  cleanup_temp
 
   local filter_arg=""
 
@@ -73,11 +81,21 @@
     cat $ANNOTATION_FILTER
   fi
 
+  local stub_arg=""
+  local impl_arg=""
+
+  if [[ "$STUB" != "" ]] ; then
+    stub_arg="--out-stub-jar $STUB"
+  fi
+  if [[ "$IMPL" != "" ]] ; then
+    impl_arg="--out-impl-jar $IMPL"
+  fi
+
   hoststubgen \
       --debug \
       --in-jar $JAR \
-      --out-stub-jar $STUB \
-      --out-impl-jar $IMPL \
+      $stub_arg \
+      $impl_arg \
       --stub-annotation \
           android.hosttest.annotation.HostSideTestStub \
       --keep-annotation \
@@ -99,12 +117,28 @@
       --keep-static-initializer-annotation \
           android.hosttest.annotation.HostSideTestStaticInitializerKeep \
       $filter_arg \
+      $EXTRA_ARGS \
       |& tee $HOSTSTUBGEN_OUT
   HOSTSTUBGEN_RC=${PIPESTATUS[0]}
   echo "HostStubGen exited with $HOSTSTUBGEN_RC"
   return 0
 }
 
+assert_file_generated() {
+  local file="$1"
+  if [[ "$file" == "" ]] ; then
+    if [[ -f "$file" ]] ; then
+      echo "HostStubGen shouldn't have generated $file"
+      return 1
+    fi
+  else
+    if ! [[ -f "$file" ]] ; then
+      echo "HostStubGen didn't generate $file"
+      return 1
+    fi
+  fi
+}
+
 run_hoststubgen_for_success() {
   run_hoststubgen "$@"
 
@@ -112,6 +146,9 @@
     echo "HostStubGen expected to finish successfully, but failed with $rc"
     return 1
   fi
+
+  assert_file_generated "$STUB"
+  assert_file_generated "$IMPL"
 }
 
 run_hoststubgen_for_failure() {
@@ -175,7 +212,6 @@
 com.unsupported.*
 "
 
-
 run_hoststubgen_for_failure "One specific class disallowed" \
     "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \
     "
@@ -189,6 +225,19 @@
 * # All other classes allowed
 "
 
+STUB="" run_hoststubgen_for_success "No stub generation" ""
+
+IMPL="" run_hoststubgen_for_success "No impl generation" ""
+
+STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" ""
+
+EXTRA_ARGS="--in-jar abc" run_hoststubgen_for_failure "Duplicate arg" \
+    "Duplicate or conflicting argument found: --in-jar" \
+    ""
+
+EXTRA_ARGS="--quiet" run_hoststubgen_for_failure "Conflicting arg" \
+    "Duplicate or conflicting argument found: --quiet" \
+    ""
 
 
 echo "All tests passed"
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 07bd2dc..dbcf3a5 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -22,7 +22,6 @@
 import com.android.hoststubgen.filters.DefaultHookInjectingFilter
 import com.android.hoststubgen.filters.FilterPolicy
 import com.android.hoststubgen.filters.ImplicitOutputFilter
-import com.android.hoststubgen.filters.KeepAllClassesFilter
 import com.android.hoststubgen.filters.OutputFilter
 import com.android.hoststubgen.filters.StubIntersectingFilter
 import com.android.hoststubgen.filters.createFilterFromTextPolicyFile
@@ -52,15 +51,15 @@
         val errors = HostStubGenErrors()
 
         // Load all classes.
-        val allClasses = loadClassStructures(options.inJar)
+        val allClasses = loadClassStructures(options.inJar.get)
 
         // Dump the classes, if specified.
-        options.inputJarDumpFile?.let {
+        options.inputJarDumpFile.ifSet {
             PrintWriter(it).use { pw -> allClasses.dump(pw) }
             log.i("Dump file created at $it")
         }
 
-        options.inputJarAsKeepAllFile?.let {
+        options.inputJarAsKeepAllFile.ifSet {
             PrintWriter(it).use {
                 pw -> allClasses.forEach {
                     classNode -> printAsTextPolicy(pw, classNode)
@@ -74,11 +73,11 @@
 
         // Transform the jar.
         convert(
-                options.inJar,
-                options.outStubJar,
-                options.outImplJar,
+                options.inJar.get,
+                options.outStubJar.get,
+                options.outImplJar.get,
                 filter,
-                options.enableClassChecker,
+                options.enableClassChecker.get,
                 allClasses,
                 errors,
         )
@@ -153,7 +152,7 @@
         // text-file based filter, which is handled by parseTextFilterPolicyFile.
 
         // The first filter is for the default policy from the command line options.
-        var filter: OutputFilter = ConstantFilter(options.defaultPolicy, "default-by-options")
+        var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options")
 
         // Next, we need a filter that resolves "class-wide" policies.
         // This is used when a member (methods, fields, nested classes) don't get any polices
@@ -163,16 +162,16 @@
 
         // Inject default hooks from options.
         filter = DefaultHookInjectingFilter(
-            options.defaultClassLoadHook,
-            options.defaultMethodCallHook,
+            options.defaultClassLoadHook.get,
+            options.defaultMethodCallHook.get,
             filter
         )
 
-        val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.let { filename ->
-            if (filename == null) {
+        val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.get.let { file ->
+            if (file == null) {
                 ClassFilter.newNullFilter(true) // Allow all classes
             } else {
-                ClassFilter.loadFromFile(filename, false)
+                ClassFilter.loadFromFile(file, false)
             }
         }
 
@@ -196,7 +195,7 @@
 
         // Next, "text based" filter, which allows to override polices without touching
         // the target code.
-        options.policyOverrideFile?.let {
+        options.policyOverrideFile.ifSet {
             filter = createFilterFromTextPolicyFile(it, allClasses, filter)
         }
 
@@ -212,11 +211,6 @@
         // Apply the implicit filter.
         filter = ImplicitOutputFilter(errors, allClasses, filter)
 
-        // Optionally keep all classes.
-        if (options.keepAllClasses) {
-            filter = KeepAllClassesFilter(filter)
-        }
-
         return filter
     }
 
@@ -237,8 +231,8 @@
      */
     private fun convert(
             inJar: String,
-            outStubJar: String,
-            outImplJar: String,
+            outStubJar: String?,
+            outImplJar: String?,
             filter: OutputFilter,
             enableChecker: Boolean,
             classes: ClassNodes,
@@ -254,8 +248,8 @@
         log.withIndent {
             // Open the input jar file and process each entry.
             ZipFile(inJar).use { inZip ->
-                ZipOutputStream(FileOutputStream(outStubJar)).use { stubOutStream ->
-                    ZipOutputStream(FileOutputStream(outImplJar)).use { implOutStream ->
+                maybeWithZipOutputStream(outStubJar) { stubOutStream ->
+                    maybeWithZipOutputStream(outImplJar) { implOutStream ->
                         val inEntries = inZip.entries()
                         while (inEntries.hasMoreElements()) {
                             val entry = inEntries.nextElement()
@@ -265,22 +259,29 @@
                         log.i("Converted all entries.")
                     }
                 }
-                log.i("Created stub: $outStubJar")
-                log.i("Created impl: $outImplJar")
+                outStubJar?.let { log.i("Created stub: $it") }
+                outImplJar?.let { log.i("Created impl: $it") }
             }
         }
         val end = System.currentTimeMillis()
         log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
     }
 
+    private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
+        if (filename == null) {
+            return block(null)
+        }
+        return ZipOutputStream(FileOutputStream(filename)).use(block)
+    }
+
     /**
      * Convert a single ZIP entry, which may or may not be a class file.
      */
     private fun convertSingleEntry(
             inZip: ZipFile,
             entry: ZipEntry,
-            stubOutStream: ZipOutputStream,
-            implOutStream: ZipOutputStream,
+            stubOutStream: ZipOutputStream?,
+            implOutStream: ZipOutputStream?,
             filter: OutputFilter,
             packageRedirector: PackageRedirectRemapper,
             enableChecker: Boolean,
@@ -316,8 +317,8 @@
             // Unknown type, we just copy it to both output zip files.
             // TODO: We probably shouldn't do it for stub jar?
             log.v("Copying: %s", entry.name)
-            copyZipEntry(inZip, entry, stubOutStream)
-            copyZipEntry(inZip, entry, implOutStream)
+            stubOutStream?.let { copyZipEntry(inZip, entry, it) }
+            implOutStream?.let { copyZipEntry(inZip, entry, it) }
         }
     }
 
@@ -346,8 +347,8 @@
     private fun processSingleClass(
             inZip: ZipFile,
             entry: ZipEntry,
-            stubOutStream: ZipOutputStream,
-            implOutStream: ZipOutputStream,
+            stubOutStream: ZipOutputStream?,
+            implOutStream: ZipOutputStream?,
             filter: OutputFilter,
             packageRedirector: PackageRedirectRemapper,
             enableChecker: Boolean,
@@ -361,7 +362,7 @@
             return
         }
         // Generate stub first.
-        if (classPolicy.policy.needsInStub) {
+        if (stubOutStream != null && classPolicy.policy.needsInStub) {
             log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy)
             log.withIndent {
                 BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
@@ -374,8 +375,8 @@
                 }
             }
         }
-        log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
-        if (classPolicy.policy.needsInImpl) {
+        if (implOutStream != null && classPolicy.policy.needsInImpl) {
+            log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
             log.withIndent {
                 BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
                     val newEntry = ZipEntry(entry.name)
@@ -415,9 +416,9 @@
             outVisitor = CheckClassAdapter(outVisitor)
         }
         val visitorOptions = BaseAdapter.Options(
-                enablePreTrace = options.enablePreTrace,
-                enablePostTrace = options.enablePostTrace,
-                enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection,
+                enablePreTrace = options.enablePreTrace.get,
+                enablePostTrace = options.enablePostTrace.get,
+                enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get,
                 errors = errors,
         )
         outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index da53487..0ae52af 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -21,21 +21,60 @@
 import java.io.FileReader
 
 /**
+ * A single value that can only set once.
+ */
+class SetOnce<T>(
+        private var value: T,
+) {
+    class SetMoreThanOnceException : Exception()
+
+    private var set = false
+
+    fun set(v: T) {
+        if (set) {
+            throw SetMoreThanOnceException()
+        }
+        if (v == null) {
+            throw NullPointerException("This shouldn't happen")
+        }
+        set = true
+        value = v
+    }
+
+    val get: T
+        get() = this.value
+
+    val isSet: Boolean
+        get() = this.set
+
+    fun <R> ifSet(block: (T & Any) -> R): R? {
+        if (isSet) {
+            return block(value!!)
+        }
+        return null
+    }
+
+    override fun toString(): String {
+        return "$value"
+    }
+}
+
+/**
  * Options that can be set from command line arguments.
  */
 class HostStubGenOptions(
         /** Input jar file*/
-        var inJar: String = "",
+        var inJar: SetOnce<String> = SetOnce(""),
 
         /** Output stub jar file */
-        var outStubJar: String = "",
+        var outStubJar: SetOnce<String?> = SetOnce(null),
 
         /** Output implementation jar file */
-        var outImplJar: String = "",
+        var outImplJar: SetOnce<String?> = SetOnce(null),
 
-        var inputJarDumpFile: String? = null,
+        var inputJarDumpFile: SetOnce<String?> = SetOnce(null),
 
-        var inputJarAsKeepAllFile: String? = null,
+        var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null),
 
         var stubAnnotations: MutableSet<String> = mutableSetOf(),
         var keepAnnotations: MutableSet<String> = mutableSetOf(),
@@ -51,27 +90,26 @@
 
         var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
 
-        var annotationAllowedClassesFile: String? = null,
+        var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null),
 
-        var defaultClassLoadHook: String? = null,
-        var defaultMethodCallHook: String? = null,
+        var defaultClassLoadHook: SetOnce<String?> = SetOnce(null),
+        var defaultMethodCallHook: SetOnce<String?> = SetOnce(null),
 
         var intersectStubJars: MutableSet<String> = mutableSetOf(),
 
-        var policyOverrideFile: String? = null,
+        var policyOverrideFile: SetOnce<String?> = SetOnce(null),
 
-        var defaultPolicy: FilterPolicy = FilterPolicy.Remove,
-        var keepAllClasses: Boolean = false,
+        var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
 
-        var logLevel: LogLevel = LogLevel.Info,
+        var logLevel: SetOnce<LogLevel> = SetOnce(LogLevel.Info),
 
-        var cleanUpOnError: Boolean = false,
+        var cleanUpOnError: SetOnce<Boolean> = SetOnce(true),
 
-        var enableClassChecker: Boolean = false,
-        var enablePreTrace: Boolean = false,
-        var enablePostTrace: Boolean = false,
+        var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
+        var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
+        var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
 
-        var enableNonStubMethodCallDetection: Boolean = true,
+        var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false),
 ) {
     companion object {
 
@@ -111,109 +149,122 @@
                     break
                 }
 
-                when (arg) {
-                    // TODO: Write help
-                    "-h", "--h" -> TODO("Help is not implemented yet")
+                // Define some shorthands...
+                fun nextArg(): String = ai.nextArgRequired(arg)
+                fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) }
+                fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
+                fun MutableSet<String>.addUniqueAnnotationArg(): String =
+                        nextArg().also { this += ensureUniqueAnnotation(it) }
 
-                    "-v", "--verbose" -> ret.logLevel = LogLevel.Verbose
-                    "-d", "--debug" -> ret.logLevel = LogLevel.Debug
-                    "-q", "--quiet" -> ret.logLevel = LogLevel.None
+                try {
+                    when (arg) {
+                        // TODO: Write help
+                        "-h", "--help" -> TODO("Help is not implemented yet")
 
-                    "--in-jar" -> ret.inJar = ai.nextArgRequired(arg).ensureFileExists()
-                    "--out-stub-jar" -> ret.outStubJar = ai.nextArgRequired(arg)
-                    "--out-impl-jar" -> ret.outImplJar = ai.nextArgRequired(arg)
+                        "-v", "--verbose" -> ret.logLevel.set(LogLevel.Verbose)
+                        "-d", "--debug" -> ret.logLevel.set(LogLevel.Debug)
+                        "-q", "--quiet" -> ret.logLevel.set(LogLevel.None)
 
-                    "--policy-override-file" ->
-                        ret.policyOverrideFile = ai.nextArgRequired(arg).ensureFileExists()
+                        "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
+                        "--out-stub-jar" -> ret.outStubJar.setNextStringArg()
+                        "--out-impl-jar" -> ret.outImplJar.setNextStringArg()
 
-                    "--clean-up-on-error" -> ret.cleanUpOnError = true
-                    "--no-clean-up-on-error" -> ret.cleanUpOnError = false
+                        "--policy-override-file" ->
+                            ret.policyOverrideFile.setNextStringArg().ensureFileExists()
 
-                    "--default-remove" -> ret.defaultPolicy = FilterPolicy.Remove
-                    "--default-throw" -> ret.defaultPolicy = FilterPolicy.Throw
-                    "--default-keep" -> ret.defaultPolicy = FilterPolicy.Keep
-                    "--default-stub" -> ret.defaultPolicy = FilterPolicy.Stub
+                        "--clean-up-on-error" -> ret.cleanUpOnError.set(true)
+                        "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
 
-                    "--keep-all-classes" -> ret.keepAllClasses = true
-                    "--no-keep-all-classes" -> ret.keepAllClasses = false
+                        "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove)
+                        "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw)
+                        "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep)
+                        "--default-stub" -> ret.defaultPolicy.set(FilterPolicy.Stub)
 
-                    "--stub-annotation" ->
-                        ret.stubAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--stub-annotation" ->
+                            ret.stubAnnotations.addUniqueAnnotationArg()
 
-                    "--keep-annotation" ->
-                        ret.keepAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--keep-annotation" ->
+                            ret.keepAnnotations.addUniqueAnnotationArg()
 
-                    "--stub-class-annotation" ->
-                        ret.stubClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--stub-class-annotation" ->
+                            ret.stubClassAnnotations.addUniqueAnnotationArg()
 
-                    "--keep-class-annotation" ->
-                        ret.keepClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--keep-class-annotation" ->
+                            ret.keepClassAnnotations.addUniqueAnnotationArg()
 
-                    "--throw-annotation" ->
-                        ret.throwAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--throw-annotation" ->
+                            ret.throwAnnotations.addUniqueAnnotationArg()
 
-                    "--remove-annotation" ->
-                        ret.removeAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--remove-annotation" ->
+                            ret.removeAnnotations.addUniqueAnnotationArg()
 
-                    "--substitute-annotation" ->
-                        ret.substituteAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--substitute-annotation" ->
+                            ret.substituteAnnotations.addUniqueAnnotationArg()
 
-                    "--native-substitute-annotation" ->
-                        ret.nativeSubstituteAnnotations +=
-                                ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--native-substitute-annotation" ->
+                            ret.nativeSubstituteAnnotations.addUniqueAnnotationArg()
 
-                    "--class-load-hook-annotation" ->
-                        ret.classLoadHookAnnotations +=
-                                ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--class-load-hook-annotation" ->
+                            ret.classLoadHookAnnotations.addUniqueAnnotationArg()
 
-                    "--keep-static-initializer-annotation" ->
-                        ret.keepStaticInitializerAnnotations +=
-                                ensureUniqueAnnotation(ai.nextArgRequired(arg))
+                        "--keep-static-initializer-annotation" ->
+                            ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg()
 
-                    "--package-redirect" ->
-                        ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
+                        "--package-redirect" ->
+                            ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
 
-                    "--annotation-allowed-classes-file" ->
-                        ret.annotationAllowedClassesFile = ai.nextArgRequired(arg)
+                        "--annotation-allowed-classes-file" ->
+                            ret.annotationAllowedClassesFile.setNextStringArg()
 
-                    "--default-class-load-hook" ->
-                        ret.defaultClassLoadHook = ai.nextArgRequired(arg)
+                        "--default-class-load-hook" ->
+                            ret.defaultClassLoadHook.setNextStringArg()
 
-                    "--default-method-call-hook" ->
-                        ret.defaultMethodCallHook = ai.nextArgRequired(arg)
+                        "--default-method-call-hook" ->
+                            ret.defaultMethodCallHook.setNextStringArg()
 
-                    "--intersect-stub-jar" ->
-                        ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists()
+                        "--intersect-stub-jar" ->
+                            ret.intersectStubJars += nextArg().ensureFileExists()
 
-                    "--gen-keep-all-file" ->
-                        ret.inputJarAsKeepAllFile = ai.nextArgRequired(arg)
+                        "--gen-keep-all-file" ->
+                            ret.inputJarAsKeepAllFile.setNextStringArg()
 
-                    // Following options are for debugging.
-                    "--enable-class-checker" -> ret.enableClassChecker = true
-                    "--no-class-checker" -> ret.enableClassChecker = false
+                        // Following options are for debugging.
+                        "--enable-class-checker" -> ret.enableClassChecker.set(true)
+                        "--no-class-checker" -> ret.enableClassChecker.set(false)
 
-                    "--enable-pre-trace" -> ret.enablePreTrace = true
-                    "--no-pre-trace" -> ret.enablePreTrace = false
+                        "--enable-pre-trace" -> ret.enablePreTrace.set(true)
+                        "--no-pre-trace" -> ret.enablePreTrace.set(false)
 
-                    "--enable-post-trace" -> ret.enablePostTrace = true
-                    "--no-post-trace" -> ret.enablePostTrace = false
+                        "--enable-post-trace" -> ret.enablePostTrace.set(true)
+                        "--no-post-trace" -> ret.enablePostTrace.set(false)
 
-                    "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true
-                    "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false
+                        "--enable-non-stub-method-check" ->
+                            ret.enableNonStubMethodCallDetection.set(true)
 
-                    "--gen-input-dump-file" -> ret.inputJarDumpFile = ai.nextArgRequired(arg)
+                        "--no-non-stub-method-check" ->
+                            ret.enableNonStubMethodCallDetection.set(false)
 
-                    else -> throw ArgumentsException("Unknown option: $arg")
+                        "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
+
+                        else -> throw ArgumentsException("Unknown option: $arg")
+                    }
+                } catch (e: SetOnce.SetMoreThanOnceException) {
+                    throw ArgumentsException("Duplicate or conflicting argument found: $arg")
                 }
             }
-            if (ret.inJar.isEmpty()) {
+            log.w(ret.toString())
+
+            if (!ret.inJar.isSet) {
                 throw ArgumentsException("Required option missing: --in-jar")
             }
-            if (ret.outStubJar.isEmpty()) {
-                throw ArgumentsException("Required option missing: --out-stub-jar")
+            if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
+                log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
+                        " $COMMAND_NAME will not generate jar files.")
             }
-            if (ret.outImplJar.isEmpty()) {
-                throw ArgumentsException("Required option missing: --out-impl-jar")
+
+            if (ret.enableNonStubMethodCallDetection.get) {
+                log.w("--enable-non-stub-method-check is not fully implemented yet." +
+                    " See the todo in doesMethodNeedNonStubCallCheck().")
             }
 
             return ret
@@ -326,7 +377,6 @@
               intersectStubJars=$intersectStubJars,
               policyOverrideFile=$policyOverrideFile,
               defaultPolicy=$defaultPolicy,
-              keepAllClasses=$keepAllClasses,
               logLevel=$logLevel,
               cleanUpOnError=$cleanUpOnError,
               enableClassChecker=$enableClassChecker,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
index 0321d9d..38ba0cc 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
@@ -28,9 +28,9 @@
     try {
         // Parse the command line arguments.
         val options = HostStubGenOptions.parseArgs(args)
-        clanupOnError = options.cleanUpOnError
+        clanupOnError = options.cleanUpOnError.get
 
-        log.level = options.logLevel
+        log.level = options.logLevel.get
 
         log.v("HostStubGen started")
         log.v("Options: $options")
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 1bcf364..d7aa0af 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -119,8 +119,12 @@
  * Write bytecode to push all the method arguments to the stack.
  * The number of arguments and their type are taken from [methodDescriptor].
  */
-fun writeByteCodeToPushArguments(methodDescriptor: String, writer: MethodVisitor) {
-    var i = -1
+fun writeByteCodeToPushArguments(
+        methodDescriptor: String,
+        writer: MethodVisitor,
+        argOffset: Int = 0,
+        ) {
+    var i = argOffset - 1
     Type.getArgumentTypes(methodDescriptor).forEach { type ->
         i++
 
@@ -159,6 +163,18 @@
 }
 
 /**
+ * Given a method descriptor, insert an [argType] as the first argument to it.
+ */
+fun prependArgTypeToMethodDescriptor(methodDescriptor: String, argType: Type): String {
+    val returnType = Type.getReturnType(methodDescriptor)
+    val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
+
+    argTypes.add(0, argType)
+
+    return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
+}
+
+/**
  * Return the "visibility" modifier from an `access` integer.
  *
  * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 4df0bfc..bc34ef0 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -1,3 +1,18 @@
+/*
+ * 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.hoststubgen.asm
 
 import com.android.hoststubgen.ClassParseException
@@ -40,6 +55,11 @@
         return findClass(name) ?: throw ClassParseException("Class $name not found")
     }
 
+    /** @return whether a class exists or not */
+    fun hasClass(name: String): Boolean {
+        return mAllClasses.containsKey(name.toJvmClassName())
+    }
+
     /** Find a field, which may not exist. */
     fun findField(
             className: String,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
new file mode 100644
index 0000000..356e1fa
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.asm.ClassNodes
+
+/**
+ * Filter that deals with Android specific heuristics.
+ */
+class AndroidHeuristicsFilter(
+        private val classes: ClassNodes,
+        val aidlPolicy: FilterPolicyWithReason?,
+        fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        if (aidlPolicy != null && classes.isAidlClass(className)) {
+            return aidlPolicy
+        }
+        return super.getPolicyForClass(className)
+    }
+}
+
+/**
+ * @return if a given class "seems like" an AIDL (top-level) class.
+ */
+private fun ClassNodes.isAidlClass(className: String): Boolean {
+    return hasClass(className) &&
+            hasClass("$className\$Stub") &&
+            hasClass("$className\$Proxy")
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt
deleted file mode 100644
index 45dd38d1..0000000
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt
+++ /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.hoststubgen.filters
-
-/**
- * An [OutputFilter] that keeps all classes by default. (but none of its members)
- *
- * We're not currently using it, but using it *might* make certain things easier. For example, with
- * this, all classes would at least be loadable.
- */
-class KeepAllClassesFilter(fallback: OutputFilter) : DelegatingFilter(fallback) {
-    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
-        // If the default visibility wouldn't keep it, change it to "keep".
-        val f = super.getPolicyForClass(className)
-        return f.promoteToKeep("keep-all-classes")
-    }
-}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 416f085..b4354ba 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -58,10 +58,12 @@
         ): OutputFilter {
     log.i("Loading offloaded annotations from $filename ...")
     log.withIndent {
-        val ret = InMemoryOutputFilter(classes, fallback)
+        val imf = InMemoryOutputFilter(classes, fallback)
 
         var lineNo = 0
 
+        var aidlPolicy: FilterPolicy? = null
+
         try {
             BufferedReader(FileReader(filename)).use { reader ->
                 var className = ""
@@ -79,6 +81,9 @@
                         continue // skip empty lines.
                     }
 
+
+                    // TODO: Method too long, break it up.
+
                     val fields = line.split(whitespaceRegex).toTypedArray()
                     when (fields[0].lowercase()) {
                         "c", "class" -> {
@@ -86,14 +91,27 @@
                                 throw ParseException("Class ('c') expects 2 fields.")
                             }
                             className = fields[1]
+
+                            val classType = resolveSpecialClass(className)
+
                             if (fields[2].startsWith("!")) {
+                                if (classType != SpecialClass.NotSpecial) {
+                                    // We could support it, but not needed at least for now.
+                                    throw ParseException(
+                                            "Special class can't have a substitution")
+                                }
                                 // It's a native-substitution.
                                 val toClass = fields[2].substring(1)
-                                ret.setNativeSubstitutionClass(className, toClass)
+                                imf.setNativeSubstitutionClass(className, toClass)
                             } else if (fields[2].startsWith("~")) {
+                                if (classType != SpecialClass.NotSpecial) {
+                                    // We could support it, but not needed at least for now.
+                                    throw ParseException(
+                                            "Special class can't have a class load hook")
+                                }
                                 // It's a class-load hook
                                 val callback = fields[2].substring(1)
-                                ret.setClassLoadHook(className, callback)
+                                imf.setClassLoadHook(className, callback)
                             } else {
                                 val policy = parsePolicy(fields[2])
                                 if (!policy.isUsableWithClasses) {
@@ -101,8 +119,20 @@
                                 }
                                 Objects.requireNonNull(className)
 
-                                // TODO: Duplicate check, etc
-                                ret.setPolicyForClass(className, policy.withReason(FILTER_REASON))
+                                when (classType) {
+                                    SpecialClass.NotSpecial -> {
+                                        // TODO: Duplicate check, etc
+                                        imf.setPolicyForClass(
+                                                className, policy.withReason(FILTER_REASON))
+                                    }
+                                    SpecialClass.Aidl -> {
+                                        if (aidlPolicy != null) {
+                                            throw ParseException(
+                                                    "Policy for AIDL classes already defined")
+                                        }
+                                        aidlPolicy = policy
+                                    }
+                                }
                             }
                         }
 
@@ -118,7 +148,7 @@
                             Objects.requireNonNull(className)
 
                             // TODO: Duplicate check, etc
-                            ret.setPolicyForField(className, name, policy.withReason(FILTER_REASON))
+                            imf.setPolicyForField(className, name, policy.withReason(FILTER_REASON))
                         }
 
                         "m", "method" -> {
@@ -135,7 +165,7 @@
 
                             Objects.requireNonNull(className)
 
-                            ret.setPolicyForMethod(className, name, signature,
+                            imf.setPolicyForMethod(className, name, signature,
                                     policy.withReason(FILTER_REASON))
                             if (policy.isSubstitute) {
                                 val fromName = fields[3].substring(1)
@@ -146,12 +176,12 @@
                                 }
 
                                 // Set the policy  for the "from" method.
-                                ret.setPolicyForMethod(className, fromName, signature,
+                                imf.setPolicyForMethod(className, fromName, signature,
                                         policy.getSubstitutionBasePolicy()
                                                 .withReason(FILTER_REASON))
 
                                 // Keep "from" -> "to" mapping.
-                                ret.setRenameTo(className, fromName, signature, name)
+                                imf.setRenameTo(className, fromName, signature, name)
                             }
                         }
 
@@ -164,10 +194,32 @@
         } catch (e: ParseException) {
             throw e.withSourceInfo(filename, lineNo)
         }
+
+        var ret: OutputFilter = imf
+        aidlPolicy?.let { policy ->
+            log.d("AndroidHeuristicsFilter enabled")
+            ret = AndroidHeuristicsFilter(
+                    classes, policy.withReason("$FILTER_REASON (AIDL)"), imf)
+        }
         return ret
     }
 }
 
+private enum class SpecialClass {
+    NotSpecial,
+    Aidl,
+}
+
+private fun resolveSpecialClass(className: String): SpecialClass {
+    if (!className.startsWith(":")) {
+        return SpecialClass.NotSpecial
+    }
+    when (className.lowercase()) {
+        ":aidl" -> return SpecialClass.Aidl
+    }
+    throw ParseException("Invalid special class name \"$className\"")
+}
+
 private fun parsePolicy(s: String): FilterPolicy {
     return when (s.lowercase()) {
         "s", "stub" -> FilterPolicy.Stub
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index e63efd0..88db15b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -19,6 +19,7 @@
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
 import com.android.hoststubgen.asm.writeByteCodeToPushArguments
 import com.android.hoststubgen.asm.writeByteCodeToReturn
 import com.android.hoststubgen.filters.FilterPolicy
@@ -285,7 +286,7 @@
      * class.
      */
     private inner class NativeSubstitutingMethodAdapter(
-            access: Int,
+            val access: Int,
             private val name: String,
             private val descriptor: String,
             signature: String?,
@@ -300,12 +301,33 @@
         }
 
         override fun visitEnd() {
-            writeByteCodeToPushArguments(descriptor, this)
+            var targetDescriptor = descriptor
+            var argOffset = 0
+
+            // For non-static native method, we need to tweak it a bit.
+            if ((access and Opcodes.ACC_STATIC) == 0) {
+                // Push `this` as the first argument.
+                this.visitVarInsn(Opcodes.ALOAD, 0)
+
+                // Update the descriptor -- add this class's type as the first argument
+                // to the method descriptor.
+                val thisType = Type.getType("L" + currentClassName + ";")
+
+                targetDescriptor = prependArgTypeToMethodDescriptor(
+                        descriptor,
+                        thisType,
+                )
+
+                // Shift the original arguments by one.
+                argOffset = 1
+            }
+
+            writeByteCodeToPushArguments(descriptor, this, argOffset)
 
             visitMethodInsn(Opcodes.INVOKESTATIC,
                     nativeSubstitutionClass,
                     name,
-                    descriptor,
+                    targetDescriptor,
                     false)
 
             writeByteCodeToReturn(descriptor, this)
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
index 20e2f87..f616ad6 100644
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-framework/README.md
@@ -14,12 +14,6 @@
 $ atest --no-bazel-mode HostStubGenTest-framework-test-host-test
 ```
 
-- With `run-ravenwood-test`
-
-```
-$ run-ravenwood-test HostStubGenTest-framework-test-host-test
-```
-
 - Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test`
 
 ```
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
index f3c0450..3bfad9b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
@@ -13,12 +13,6 @@
 $ atest hoststubgen-test-tiny-test
 ```
 
-- With `run-ravenwood-test` should work too. This is the proper way to run it.
-
-```
-$ run-ravenwood-test hoststubgen-test-tiny-test
-```
-
 - `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without
   using the build system. This is useful for debugging the tool.
 
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 3474ae4..214de59 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -223,6 +223,103 @@
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD]
     )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+SourceFile: "IPretendingAidl.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+InnerClasses:
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub;
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+SourceFile: "IPretendingAidl.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+InnerClasses:
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 61
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 3
+}
+SourceFile: "IPretendingAidl.java"
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -1718,7 +1815,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 5, attributes: 2
+  interfaces: 0, fields: 1, methods: 8, attributes: 2
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1767,6 +1868,40 @@
         Start  Length  Slot  Name   Signature
             0       6     0  arg1   J
             0       6     2  arg2   J
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1     v   I
+
+  public native int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1   arg   I
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeInvisibleAnnotations:
@@ -1782,9 +1917,9 @@
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 2
+  interfaces: 0, fields: 0, methods: 4, attributes: 2
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1826,6 +1961,22 @@
         Start  Length  Slot  Name   Signature
             0       4     0  arg1   J
             0       4     2  arg2   J
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: getfield      #x                  // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       7     1   arg   I
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index a1aae8a..9031228 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1,3 +1,105 @@
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 61
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 4
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -1039,7 +1141,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1080,6 +1186,32 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public native int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 29626f2..e01f49b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -205,6 +205,118 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub;
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+InnerClasses:
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 61
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 4
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -1650,7 +1762,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1710,6 +1826,46 @@
         Start  Length  Slot  Name   Signature
             0       6     0  arg1   J
             0       6     2  arg2   J
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1     v   I
+
+  public int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1   arg   I
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
@@ -1732,7 +1888,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  interfaces: 0, fields: 0, methods: 4, attributes: 3
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1792,6 +1948,28 @@
         Start  Length  Slot  Name   Signature
            15       4     0  arg1   J
            15       4     2  arg2   J
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeNonStaticAddToValue
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+        x: iload_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           15       7     1   arg   I
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index a1aae8a..9031228 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1,3 +1,105 @@
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 61
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 4
+}
+InnerClasses:
+  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -1039,7 +1141,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1080,6 +1186,32 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public native int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index ed7e7d3..5246355 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -289,6 +289,167 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+}
+InnerClasses:
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub;
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+}
+InnerClasses:
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 61
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+InnerClasses:
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
@@ -2108,7 +2269,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 6, attributes: 3
+  interfaces: 0, fields: 1, methods: 9, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
   private static {};
     descriptor: ()V
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2193,6 +2358,56 @@
         Start  Length  Slot  Name   Signature
            11       6     0  arg1   J
            11       6     2  arg2   J
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String setValue
+         x: ldc           #x                 // String (I)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       6     1     v   I
+
+  public int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeNonStaticAddToValue_should_be_like_this
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       6     1   arg   I
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
@@ -2215,7 +2430,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 3
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
   private static {};
     descriptor: ()V
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2300,6 +2515,33 @@
         Start  Length  Slot  Name   Signature
            26       4     0  arg1   J
            26       4     2  arg2   J
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeNonStaticAddToValue
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+        x: ldc           #x                 // String nativeNonStaticAddToValue
+        x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+        x: iload_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           26       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           26       7     1   arg   I
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 079d2a8..9b6b6e4 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -15,3 +15,6 @@
 
 # Class load hook
 class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+
+# Heuristics rule: Stub all the AIDL classes.
+class :aidl stubclass
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java
new file mode 100644
index 0000000..583e13c
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.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.hoststubgen.test.tinyframework;
+
+/**
+ * An interface that matches the "AIDL detection heuristics' logic.
+ *
+ * The "class :aidl" line in the text policy file will control the visibility of it.
+ */
+public interface IPretendingAidl {
+    public static class Stub {
+        public static int addOne(int a) {
+            return a + 1;
+        }
+    }
+
+    public static class Proxy {
+        public static int addTwo(int a) {
+            return a + 2;
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index c151dcc..e7b5d9f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -32,4 +32,16 @@
     public static long nativeLongPlus_should_be_like_this(long arg1, long arg2) {
         return TinyFrameworkNative_host.nativeLongPlus(arg1, arg2);
     }
+
+    int value;
+
+    public void setValue(int v) {
+        this.value = v;
+    }
+
+    public native int nativeNonStaticAddToValue(int arg);
+
+    public int nativeNonStaticAddToValue_should_be_like_this(int arg) {
+        return TinyFrameworkNative_host.nativeNonStaticAddToValue(this, arg);
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
index 48f7dea..749ebaa 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -28,4 +28,10 @@
     public static long nativeLongPlus(long arg1, long arg2) {
         return arg1 + arg2;
     }
+
+    // Note, the method must be static even for a non-static native method, but instead it
+    // must take the "source" instance as the first argument.
+    public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) {
+        return source.value + arg;
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index b015661..0d52791 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -152,6 +152,13 @@
     }
 
     @Test
+    public void testNativeSubstitutionClass_nonStatic() {
+        TinyFrameworkNative instance = new TinyFrameworkNative();
+        instance.setValue(5);
+        assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
+    }
+
+    @Test
     public void testExitLog() {
         thrown.expect(RuntimeException.class);
         thrown.expectMessage("Outer exception");
@@ -251,7 +258,7 @@
         assertThat(TinyFrameworkEnumSimple.valueOf("DOG").ordinal()).isEqualTo(1);
 
         assertThat(TinyFrameworkEnumSimple.values()).isEqualTo(
-                new TinyFrameworkEnumSimple[] {
+                new TinyFrameworkEnumSimple[]{
                         TinyFrameworkEnumSimple.CAT,
                         TinyFrameworkEnumSimple.DOG,
                 }
@@ -271,11 +278,17 @@
         assertThat(TinyFrameworkEnumComplex.valueOf("BLUE").ordinal()).isEqualTo(2);
 
         assertThat(TinyFrameworkEnumComplex.values()).isEqualTo(
-                new TinyFrameworkEnumComplex[] {
+                new TinyFrameworkEnumComplex[]{
                         TinyFrameworkEnumComplex.RED,
                         TinyFrameworkEnumComplex.GREEN,
                         TinyFrameworkEnumComplex.BLUE,
                 }
         );
     }
+
+    @Test
+    public void testAidlHeuristics() {
+        assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2);
+        assertThat(IPretendingAidl.Proxy.addTwo(1)).isEqualTo(3);
+    }
 }
diff --git a/tools/hoststubgen/scripts/Android.bp b/tools/hoststubgen/scripts/Android.bp
index 5da805e..b1ba07e 100644
--- a/tools/hoststubgen/scripts/Android.bp
+++ b/tools/hoststubgen/scripts/Android.bp
@@ -18,9 +18,3 @@
     tools: ["dump-jar"],
     cmd: "$(location dump-jar) -s -o $(out) $(in)",
 }
-
-sh_binary_host {
-    name: "run-ravenwood-test",
-    src: "run-ravenwood-test",
-    visibility: ["//visibility:public"],
-}
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 2dac089..82faa91 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,10 +22,10 @@
 READY_TEST_MODULES=(
   HostStubGenTest-framework-all-test-host-test
   hoststubgen-test-tiny-test
+  CtsUtilTestCasesRavenwood
 )
 
 MUST_BUILD_MODULES=(
-    run-ravenwood-test
     "${NOT_READY_TEST_MODULES[*]}"
     HostStubGenTest-framework-test
 )
@@ -51,8 +51,6 @@
 # run ./scripts/build-framework-hostside-jars-without-genrules.sh
 
 # These tests should all pass.
-run-ravenwood-test ${READY_TEST_MODULES[*]}
-
-run atest CtsUtilTestCasesRavenwood
+run atest ${READY_TEST_MODULES[*]}
 
 echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-ravenwood-test b/tools/hoststubgen/scripts/run-ravenwood-test
deleted file mode 100755
index 9bbb859..0000000
--- a/tools/hoststubgen/scripts/run-ravenwood-test
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/bin/bash
-# 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.
-
-set -e
-
-# Script to run a "Ravenwood" host side test.
-#
-# A proper way to run these tests is to use `atest`, but `atest` has a known issue of loading
-# unrelated jar files as the class path, so for now we use this script to run host side tests.
-
-# Copy (with some changes) some functions from ../common.sh, so this script can be used without it.
-
-m() {
-  if (( $SKIP_BUILD )) ; then
-    echo "Skipping build: $*" 1>&2
-    return 0
-  fi
-  run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
-}
-
-run() {
-  echo "Running: $*" 1>&2
-  "$@"
-}
-
-run_junit_test_jar() {
-  local jar="$1"
-  echo "Starting test: $jar ..."
-  run cd "${jar%/*}"
-
-  run ${JAVA:-java} $JAVA_OPTS \
-      -cp $jar \
-      org.junit.runner.JUnitCore \
-      com.android.hoststubgen.hosthelper.HostTestSuite || return 1
-  return 0
-}
-
-help() {
-  cat <<'EOF'
-
-  run-ravenwood-test -- Run Ravenwood host tests
-
-  Usage:
-    run-ravenwood-test [options] MODULE-NAME ...
-
-  Options:
-    -h: Help
-    -t: Run test only, without building
-    -b: Build only, without running
-
-  Example:
-    run-ravenwood-test HostStubGenTest-framework-test-host-test
-
-EOF
-}
-
-#-------------------------------------------------------------------------
-# Parse options
-#-------------------------------------------------------------------------
-build=0
-test=0
-
-while getopts "htb" opt; do
-  case "$opt" in
-    h) help; exit 0 ;;
-    t)
-      test=1
-      ;;
-    b)
-      build=1
-      ;;
-  esac
-done
-shift $(($OPTIND - 1))
-
-# If neither -t nor -b is provided, then build and run./
-if (( ( $build + $test ) == 0 )) ; then
-  build=1
-  test=1
-fi
-
-
-modules=("${@}")
-
-if (( "${#modules[@]}" == 0 )); then
-  help
-  exit 1
-fi
-
-#-------------------------------------------------------------------------
-# Build
-#-------------------------------------------------------------------------
-if (( $build )) ; then
-  run m "${modules[@]}"
-fi
-
-#-------------------------------------------------------------------------
-# Run
-#-------------------------------------------------------------------------
-
-failures=0
-if (( test )) ; then
-  for module in "${modules[@]}"; do
-    if run_junit_test_jar "$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/${module}/${module}.jar"; then
-      : # passed.
-    else
-      failures=$(( failures + 1 ))
-    fi
-  done
-
-  if (( $failures > 0 )) ; then
-    echo "$failures test jar(s) failed." 1>&2
-    exit 2
-  fi
-fi
-
-exit 0
\ No newline at end of file